From 68da6f5d46147cb78ad24e1c4f94260ab02c78d6 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 11 May 2015 20:48:21 +0200 Subject: [PATCH 01/15] Adds README file. --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2a6461 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# Flysystem Adapter for Github + +[![Latest Version](https://img.shields.io/github/release/potherca/flysystem-github.svg?style=flat-square)](https://github.com/potherca/flysystem-github/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Build Status](https://img.shields.io/travis/potherca/flysystem-github/master.svg?style=flat-square)](https://travis-ci.org/potherca/flysystem-github) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/potherca/flysystem-github.svg?style=flat-square)](https://scrutinizer-ci.com/g/potherca/flysystem-github/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/potherca/flysystem-github.svg?style=flat-square)](https://scrutinizer-ci.com/g/potherca/flysystem-github) +[![Total Downloads](https://img.shields.io/packagist/dt/potherca/flysystem-github.svg?style=flat-square)](https://packagist.org/packages/potherca/flysystem-github) + + +## Install + +Via Composer + +``` bash +$ composer require potherca/flysystem-github +``` + +## Usage + +The Github adapter can be used *without* credentials to do read-only actions on +public repositories. To save changes or read from private repositories, +credentials are required. + +To avoid the Github API limit or to save traffic caching can be utilized. + +### Basic Usage + +```php +use Github\Client; +use League\Flysystem\Filesystem; +use Potherca\Flysystem\Github\GithubAdapter; + +$project = 'thephpleague/flysystem'; + +$client = new Client(); +$adapter = new GithubAdapter($client, $project); + +$filesystem = new Filesystem($adapter); +``` + +### Authentication + +```php +use Github\Client; +use League\Flysystem\Filesystem; +use Potherca\Flysystem\Github\GithubAdapter; + +$project = 'thephpleague/flysystem'; + +$client = new Client(); +$client->authenticate($token, null, Client::AUTH_HTTP_TOKEN); +// or $client->authenticate($username, $password, Client::AUTH_HTTP_PASSWORD); +$adapter = new GithubAdapter($client, $project); + +$filesystem = new Filesystem($adapter); +``` + +For full details, see the [php-github-api documentation concerning authentication](https://github.com/KnpLabs/php-github-api/blob/master/doc/security.md). + +### Cache Usage + +```php +use Github\Client; +use Github\HttpClient\CachedHttpClient as CachedClient; +use Github\HttpClient\Cache\FilesystemCache as Cache; +use League\Flysystem\Filesystem; +use Potherca\Flysystem\Github\GithubAdapter; + +$project = 'thephpleague/flysystem'; + +$cache = new Cache('/tmp/github-api-cache') +$cacheClient = new CachedClient(); +$cacheClient->setCache($cache); + +$client = new Client($cacheClient); +$adapter = new GithubAdapter($client, $project); + +$filesystem = new Filesystem($adapter); +``` + +## Testing + +``` bash +$ composer test +``` + +## Contributing + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details. + +## Security + +If you discover any security related issues, please email potherca@gmail.com instead of using the issue tracker. + +## Credits + +- [Potherca](https://github.com/potherca) + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. From ba557cc1c62eb8dc145ce93ab9e8b21cd9716545 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 11 May 2015 20:48:52 +0200 Subject: [PATCH 02/15] Adds MIT License. --- LICENSE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..848cf83 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2015 Potherca + +> 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. From 347356da52271ace42b81241e2da773a80cdd19b Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 11 May 2015 20:49:43 +0200 Subject: [PATCH 03/15] Adds Composer file stating dependencies. --- composer.json | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8e0aa82 --- /dev/null +++ b/composer.json @@ -0,0 +1,49 @@ +{ + "name": "potherca/flysystem-github", + "description": "Flysystem adapter for Github", + "keywords": [ + "potherca", + "flysystem-github", + "flysystem-adapter", + "flysystem", + "github", + "adapter" + ], + "homepage": "https://github.com/potherca/flysystem-github", + "license": "MIT", + "authors": [ + { + "name": "Potherca", + "email": "potherca@gmail.com", + "homepage": "http://pother.ca/", + "role": "Developer" + } + ], + "require": { + "php" : ">=5.3.0", + "knplabs/github-api": "^1.4", + "league/flysystem": "^1.0" + }, + "require-dev": { + "phpunit/phpunit" : "4.*", + "scrutinizer/ocular": "~1.1" + }, + "autoload": { + "psr-4": { + "Potherca\\Flysystem\\Github\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Potherca\\Flysystem\\Github\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "scripts": { + "test" : "phpunit" + } +} From 4584c4b00de9bf10eb1821754a4c5b5053ea5f73 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 11 May 2015 20:50:54 +0200 Subject: [PATCH 04/15] Adds Contributing guidelines. --- CONTRIBUTING.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1631d2b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/potherca/flysystem-github). + + +## Pull Requests + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. + +- **Create feature branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + + +## Running Tests + +``` bash +$ composer test +``` + + +**Happy coding**! From 94423c85675baaf28c153c7a6c59784713fad13a Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 11 May 2015 20:52:01 +0200 Subject: [PATCH 05/15] Adds PHPUnit Configuration file. --- phpunit.xml.dist | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 phpunit.xml.dist diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..68bbdfd --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + tests + + + + + src/ + + + + + + + + + + From 2e02abf523928324aacdac010648a7bdc9e2bdd8 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 11 May 2015 21:19:43 +0200 Subject: [PATCH 06/15] Adds .gitignore file. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82405c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +vendor + +composer.lock +phpunit.xml \ No newline at end of file From 644ab273bbfc69e01baf0c3b95c8634ae53ce712 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Tue, 12 May 2015 22:55:29 +0200 Subject: [PATCH 07/15] Adds first draft github adapter class. --- src/GithubAdapter.php | 416 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 src/GithubAdapter.php diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php new file mode 100644 index 0000000..db7bccb --- /dev/null +++ b/src/GithubAdapter.php @@ -0,0 +1,416 @@ + null, + self::COMMITTER_MAIL => null, + ); + + /** + * Constructor. + * + * @param Client $client + * @param Settings $settings + */ + public function __construct( + Client $client, + Settings $settings + ) { + $this->client = $client; + $this->repository = $settings->repository; + $this->reference = $settings->reference; + + //@TODO: If $client contains credentials, $settings MUST contains author info! + } + + /** + * Write a new file. + * + * @param string $path + * @param string $contents + * @param Config $config Config object + * + * @return array|false false on failure file meta data on success + */ + public function write($path, $contents, Config $config) + { + // Create a file + $fileInfo = $this->getRepositoryContents()->create( + $this->vendor, + $this->package, + $path, + $contents, + $this->commitMessage, + $this->branch, + $this->committer + ); + + return $fileInfo; + } + + /** + * Write a new file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object + * + * @return array|false false on failure file meta data on success + */ + public function writeStream($path, $resource, Config $config) + { + // TODO: Implement writeStream() method. + } + + /** + * Update a file. + * + * @param string $path + * @param string $contents + * @param Config $config Config object + * + * @return array|false false on failure file meta data on success + */ + public function update($path, $contents, Config $config) + { + $oldFile = $this->getMetadata($path); + + $fileInfo = $this->getRepositoryContents()->update( + $this->vendor, + $this->package, + $path, + $contents, + $this->commitMessage, + $oldFile['sha'], + $this->branch, + $this->committer + ); + + return $fileInfo; + } + + /** + * Update a file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object + * + * @return array|false false on failure file meta data on success + */ + public function updateStream($path, $resource, Config $config) + { + // TODO: Implement updateStream() method. + } + + /** + * Rename a file. + * + * @param string $path + * @param string $newpath + * + * @return bool + */ + public function rename($path, $newpath) + { + // TODO: Implement rename() method. + } + + /** + * Copy a file. + * + * @param string $path + * @param string $newpath + * + * @return bool + */ + public function copy($path, $newpath) + { + // TODO: Implement copy() method. + } + + /** + * Delete a file. + * + * @param string $path + * + * @return bool + */ + public function delete($path) + { + $oldFile = $this->getMetadata($path); + + $fileInfo = $this->getRepositoryContents()->rm( + $this->vendor, + $this->package, + $path, + $this->commitMessage, + $oldFile['sha'], + $this->branch, + $this->committer + ); + + return $fileInfo; + } + + /** + * Delete a directory. + * + * @param string $dirname + * + * @return bool + */ + public function deleteDir($dirname) + { + // TODO: Implement deleteDir() method. + } + + /** + * Create a directory. + * + * @param string $dirname directory name + * @param Config $config + * + * @return array|false + */ + public function createDir($dirname, Config $config) + { + // TODO: Implement createDir() method. + } + + /** + * Set the visibility for a file. + * + * @param string $path + * @param string $visibility + * + * @return array|false file meta data + */ + public function setVisibility($path, $visibility) + { + // TODO: Implement setVisibility() method. + } + + /** + * Check that a file or directory exists in the repository + * + * @param string $path + * + * @return array|bool|null + */ + public function has($path) + { + $fileExists = $this->getRepositoryContents()->exists( + $this->vendor, + $this->package, + $path, + $this->reference + ); + + return $fileExists; + } + + /** + * Download a file + * + * @param string $path + * + * @return array|false + */ + public function read($path) + { + $fileContent = $this->getRepositoryContents()->download( + $this->vendor, + $this->package, + $path, $this->reference + ); + + return $fileContent; + } + + /** + * Read a file as a stream. + * + * @param string $path + * + * @return array|false + */ + public function readStream($path) + { + // TODO: Implement readStream() method. + } + + /** + * List contents of a directory. + * + * @param string $directory + * @param bool $recursive + * + * @return array + */ + public function listContents($directory = '', $recursive = false) + { + $directoryInfo = $this->getMetadata($directory); + // @TODO: Read file names from $directoryInfo + } + + /** + * Get all the meta data of a file or directory. + * + * @param string $path + * + * @return array|false + */ + public function getMetadata($path) + { + // Get information about a repository file or directory + $fileInfo = $this->getRepositoryContents()->show( + $this->vendor, + $this->package, + $path, + $this->reference + ); + + return $fileInfo; + } + + /** + * Get all the meta data of a file or directory. + * + * @param string $path + * + * @return array|false + */ + public function getSize($path) + { + // TODO: Implement getSize() method. + } + + /** + * Get the mimetype of a file. + * + * @param string $path + * + * @return array|false + */ + public function getMimetype($path) + { + // TODO: Implement getMimetype() method. + } + + /** + * Get the timestamp of a file. + * + * @param string $path + * + * @return array|false + */ + public function getTimestamp($path) + { + // List commits for a file + $commits = $this->getRepository()->commits()->all( + $this->vendor, + $this->package, + array( + 'sha' => $this->branch, + 'path' => $path + ) + ); + //@TODO: Get timestamp from first commit in $commits + + } + + /** + * Get the visibility of a file. + * + * @param string $path + * + * @return array|false + */ + public function getVisibility($path) + { + // TODO: Implement getVisibility() method. + } + + /** + * @param string $path File or Folder path + * @param string $content The content to write to the given file + * + * @throws \Github\Exception\ErrorException + * @throws \Github\Exception\MissingArgumentException + */ + private function foo($path, $content = '') + { + $this->branch = $this->reference; + + $this->committerName = 'KnpLabs'; + $this->committerEmail = 'info@knplabs.com'; + $this->vendor = 'knp-labs'; + $this->package = 'php-github-api'; + + $this->committer = array( + self::COMMITTER_NAME => $this->committerName, + self::COMMITTER_MAIL => $this->committerEmail + ); + $this->commitMessage = 'Edited with Flysystem'; + + + // https://github.com/thephpleague/flysystem/wiki/Adapter-Internals + /*********** Meta Data Values ********** + ------------------------------------- + key | description + ------------------------------------- + type | file or dir + path | path to the file or dir + contents | file contents (string) + stream | stream (resource) + visibility | public or private + timestamp | modified time + ------------------------------------- + + When an adapter can not provide the metadata with the key that's required to satisfy the call, false should be returned. + + */ + } + + /** + * @return \Github\Api\Repository\Contents + */ + private function getRepositoryContents() + { + return $this->getRepository()->contents(); + } + + /** + * @return Repo + */ + private function getRepository() + { + return $this->client->api('repo'); + } +} \ No newline at end of file From cbe2591b986cb533ca12cee5c4ab30f20929aebf Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Sat, 23 May 2015 00:12:39 +0200 Subject: [PATCH 08/15] Adds first draft for listing directory content. --- src/GithubAdapter.php | 119 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php index db7bccb..c9850fd 100644 --- a/src/GithubAdapter.php +++ b/src/GithubAdapter.php @@ -2,6 +2,7 @@ namespace Potherca\Flysystem\Github; +use Github\Api\GitData; use Github\Api\Repo; use Github\Client; use League\Flysystem\Adapter\AbstractAdapter; @@ -9,11 +10,25 @@ class GithubAdapter extends AbstractAdapter { + const KEY_BLOB = 'blob'; + const KEY_CONTENTS = 'contents'; + const KEY_DIRECTORY = 'dir'; + const KEY_FILE = 'file'; + const KEY_GIT_DATA = 'git'; + const KEY_REPO = 'repo'; + const KEY_STREAM = 'stream'; + const KEY_TIMESTAMP = 'timestamp'; + const KEY_TREE = 'tree'; + const KEY_TYPE = 'type'; + const KEY_VISIBILITY = 'visibility'; + const BRANCH_MASTER = 'master'; const COMMITTER_MAIL = 'email'; const COMMITTER_NAME = 'name'; + const VISIBILITY_PRIVATE = 'private'; + const VISIBILITY_PUBLIC = 'public'; /** @var Client */ protected $client; @@ -30,6 +45,7 @@ class GithubAdapter extends AbstractAdapter self::COMMITTER_NAME => null, self::COMMITTER_MAIL => null, ); + private $visibility; /** * Constructor. @@ -45,6 +61,8 @@ public function __construct( $this->repository = $settings->repository; $this->reference = $settings->reference; + list($this->vendor, $this->package) = explode('/', $this->repository); + //@TODO: If $client contains credentials, $settings MUST contains author info! } @@ -275,8 +293,15 @@ public function readStream($path) */ public function listContents($directory = '', $recursive = false) { - $directoryInfo = $this->getMetadata($directory); - // @TODO: Read file names from $directoryInfo + if ($recursive === true) { + $info = $this->getTree($recursive); + $result = $this->normalizeTree($info); + } else { + $metadata = $this->getMetadata($directory); + $result = $this->normalizeMetaData($metadata); + } + + return $result; } /** @@ -354,7 +379,20 @@ public function getTimestamp($path) */ public function getVisibility($path) { - // TODO: Implement getVisibility() method. + if ($this->visibility === null) { + $repo = $this->getRepository()->show( + $this->vendor, + $this->package + ); + + if ($repo[self::VISIBILITY_PRIVATE] === true) { + $this->visibility = self::VISIBILITY_PRIVATE; + } else { + $this->visibility = self::VISIBILITY_PUBLIC; + } + } + + return $this->visibility; } /** @@ -411,6 +449,77 @@ private function getRepositoryContents() */ private function getRepository() { - return $this->client->api('repo'); + return $this->client->api(self::KEY_REPO); + } + + /** + * @return GitData + */ + private function getGitData() + { + return $this->client->api(self::KEY_GIT_DATA); + } + + /** + * @param $recursive + * @return \Guzzle\Http\EntityBodyInterface|mixed|string + */ + private function getTree($recursive) + { + $trees = $this->getGitData()->trees(); + + $info = $trees->show( + $this->vendor, + $this->package, + $this->reference, + $recursive + ); + + return $info[self::KEY_TREE]; + } + + /** + * @param $info + * @return array + */ + private function normalizeMetaData($info) + { + $result = []; + + foreach ($info as $entry) { + $entry[self::KEY_CONTENTS] = false; + $entry[self::KEY_STREAM] = false; + $entry[self::KEY_TIMESTAMP] = false; + $entry[self::KEY_VISIBILITY] = $this->getVisibility(null); + $result[] = $entry; + } + + return $result; + } + + private function normalizeTree($info) + { + $result = []; + + foreach ($info as $entry) { + switch ($entry[self::KEY_TYPE]) { + case self::KEY_BLOB: + $entry[self::KEY_TYPE] = self::KEY_FILE; + break; + + case self::KEY_TREE: + $entry[self::KEY_TYPE] = self::KEY_DIRECTORY; + break; + } + + $entry[self::KEY_CONTENTS] = false; + $entry[self::KEY_STREAM] = false; + $entry[self::KEY_TIMESTAMP] = false; + //@CHECKME: Should this be the same for the entire repo or the file 'mode' + $entry[self::KEY_VISIBILITY] = $this->getVisibility(null); + $result[] = $entry; + } + + return $result; } -} \ No newline at end of file +} From 8edb7f3098af3c4938e6c85f027e28eeac26b600 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Sun, 24 May 2015 21:25:36 +0200 Subject: [PATCH 09/15] Adds first draft for "read" actions and add API authentication - Adds guesswork logic for getMimetype - Adds logic for getTimestamp - Improves logic for getVisibility - Improves normalization - Adds filter for tree metadata --- src/GithubAdapter.php | 285 ++++++++++++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 96 deletions(-) diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php index c9850fd..4fe8823 100644 --- a/src/GithubAdapter.php +++ b/src/GithubAdapter.php @@ -6,16 +6,36 @@ use Github\Api\Repo; use Github\Client; use League\Flysystem\Adapter\AbstractAdapter; +use League\Flysystem\Adapter\Polyfill\StreamedTrait; +use League\Flysystem\AdapterInterface; use League\Flysystem\Config; +use League\Flysystem\Util; + +/** + * @FIXME: Once the functionality is clear, all of the Github Client calls need to be moved + * to Potherca\Flysystem\GithubClient which is a composite of the Github\Api\* and + * the Github\Client classes. + */ class GithubAdapter extends AbstractAdapter { + use StreamedTrait; + + /* + stream stream (resource) + */ const KEY_BLOB = 'blob'; const KEY_CONTENTS = 'contents'; const KEY_DIRECTORY = 'dir'; const KEY_FILE = 'file'; + const KEY_FILENAME = 'basename'; const KEY_GIT_DATA = 'git'; + const KEY_MODE = 'mode'; + const KEY_NAME = 'name'; + const KEY_PATH = 'path'; const KEY_REPO = 'repo'; + const KEY_SHA = 'sha'; + const KEY_SIZE = 'size'; const KEY_STREAM = 'stream'; const KEY_TIMESTAMP = 'timestamp'; const KEY_TREE = 'tree'; @@ -39,17 +59,14 @@ class GithubAdapter extends AbstractAdapter private $committerName; private $package; private $reference; - private $repository; private $vendor; private $committer = array( self::COMMITTER_NAME => null, self::COMMITTER_MAIL => null, ); - private $visibility; + private $credentials = []; /** - * Constructor. - * * @param Client $client * @param Settings $settings */ @@ -58,12 +75,16 @@ public function __construct( Settings $settings ) { $this->client = $client; + + /* @NOTE: If $client contains `credentials` but not an `author` we are + * still in `read-only` mode. + */ + //@CHECKME Is it really necessary that we man-handle each setting? + // Can't we just use the settings object directly instead? $this->repository = $settings->repository; $this->reference = $settings->reference; - list($this->vendor, $this->package) = explode('/', $this->repository); - - //@TODO: If $client contains credentials, $settings MUST contains author info! + $this->credentials = $settings->credentials; } /** @@ -78,7 +99,7 @@ public function __construct( public function write($path, $contents, Config $config) { // Create a file - $fileInfo = $this->getRepositoryContents()->create( + $fileInfo = $this->repositoryContents()->create( $this->vendor, $this->package, $path, @@ -102,7 +123,7 @@ public function write($path, $contents, Config $config) */ public function writeStream($path, $resource, Config $config) { - // TODO: Implement writeStream() method. + // @TODO: Use writeStream() trait, once write() has been implemented } /** @@ -118,13 +139,13 @@ public function update($path, $contents, Config $config) { $oldFile = $this->getMetadata($path); - $fileInfo = $this->getRepositoryContents()->update( + $fileInfo = $this->repositoryContents()->update( $this->vendor, $this->package, $path, $contents, $this->commitMessage, - $oldFile['sha'], + $oldFile[self::KEY_SHA], $this->branch, $this->committer ); @@ -183,7 +204,7 @@ public function delete($path) { $oldFile = $this->getMetadata($path); - $fileInfo = $this->getRepositoryContents()->rm( + $fileInfo = $this->repositoryContents()->rm( $this->vendor, $this->package, $path, @@ -243,18 +264,16 @@ public function setVisibility($path, $visibility) */ public function has($path) { - $fileExists = $this->getRepositoryContents()->exists( + return $this->repositoryContents()->exists( $this->vendor, $this->package, $path, $this->reference ); - - return $fileExists; } /** - * Download a file + * Read a file * * @param string $path * @@ -262,46 +281,27 @@ public function has($path) */ public function read($path) { - $fileContent = $this->getRepositoryContents()->download( + $fileContent = $this->repositoryContents()->download( $this->vendor, $this->package, - $path, $this->reference + $path, + $this->reference ); - return $fileContent; - } - - /** - * Read a file as a stream. - * - * @param string $path - * - * @return array|false - */ - public function readStream($path) - { - // TODO: Implement readStream() method. + return [self::KEY_CONTENTS => $fileContent]; } /** * List contents of a directory. * - * @param string $directory + * @param string $path * @param bool $recursive * * @return array */ - public function listContents($directory = '', $recursive = false) + public function listContents($path = '/', $recursive = false) { - if ($recursive === true) { - $info = $this->getTree($recursive); - $result = $this->normalizeTree($info); - } else { - $metadata = $this->getMetadata($directory); - $result = $this->normalizeMetaData($metadata); - } - - return $result; + return $this->internalMetadata($path, $recursive); } /** @@ -314,7 +314,7 @@ public function listContents($directory = '', $recursive = false) public function getMetadata($path) { // Get information about a repository file or directory - $fileInfo = $this->getRepositoryContents()->show( + $fileInfo = $this->repositoryContents()->show( $this->vendor, $this->package, $path, @@ -333,7 +333,7 @@ public function getMetadata($path) */ public function getSize($path) { - // TODO: Implement getSize() method. + return $this->getMetadata($path); } /** @@ -345,7 +345,19 @@ public function getSize($path) */ public function getMimetype($path) { - // TODO: Implement getMimetype() method. + //@NOTE: The github API does not return a MIME type, so we have to guess :-( + if (strrpos($path, '.') > 1) { + $extension = substr($path, strrpos($path, '.')+1); + } + + if (isset($extension)) { + $mimeType = Util\MimeType::detectByFileExtension($extension) ?: 'text/plain'; + } else { + $content = $this->read($path); + $mimeType = Util\MimeType::detectByContent($content['contents']); + } + + return ['mimetype' => $mimeType]; } /** @@ -358,7 +370,7 @@ public function getMimetype($path) public function getTimestamp($path) { // List commits for a file - $commits = $this->getRepository()->commits()->all( + $commits = $this->repository()->commits()->all( $this->vendor, $this->package, array( @@ -366,8 +378,12 @@ public function getTimestamp($path) 'path' => $path ) ); - //@TODO: Get timestamp from first commit in $commits + $updated = array_shift($commits); + //@NOTE: $created = array_pop($commits); + + $time = new \DateTime($updated['commit']['committer']['date']); + return ['timestamp' => $time->getTimestamp()]; } /** @@ -379,20 +395,8 @@ public function getTimestamp($path) */ public function getVisibility($path) { - if ($this->visibility === null) { - $repo = $this->getRepository()->show( - $this->vendor, - $this->package - ); - - if ($repo[self::VISIBILITY_PRIVATE] === true) { - $this->visibility = self::VISIBILITY_PRIVATE; - } else { - $this->visibility = self::VISIBILITY_PUBLIC; - } - } - - return $this->visibility; + $metadata = $this->internalMetadata($path, false); + return $metadata[0]; } /** @@ -439,34 +443,34 @@ private function foo($path, $content = '') /** * @return \Github\Api\Repository\Contents */ - private function getRepositoryContents() + private function repositoryContents() { - return $this->getRepository()->contents(); + return $this->repository()->contents(); } /** * @return Repo */ - private function getRepository() + private function repository() { - return $this->client->api(self::KEY_REPO); + return $this->fetchApi(self::KEY_REPO); } /** * @return GitData */ - private function getGitData() + private function gitData() { - return $this->client->api(self::KEY_GIT_DATA); + return $this->fetchApi(self::KEY_GIT_DATA); } /** * @param $recursive * @return \Guzzle\Http\EntityBodyInterface|mixed|string */ - private function getTree($recursive) + private function trees($recursive) { - $trees = $this->getGitData()->trees(); + $trees = $this->gitData()->trees(); $info = $trees->show( $this->vendor, @@ -478,48 +482,137 @@ private function getTree($recursive) return $info[self::KEY_TREE]; } - /** - * @param $info - * @return array - */ - private function normalizeMetaData($info) + private function normalizeMetadata($metadata) { $result = []; - foreach ($info as $entry) { - $entry[self::KEY_CONTENTS] = false; - $entry[self::KEY_STREAM] = false; - $entry[self::KEY_TIMESTAMP] = false; - $entry[self::KEY_VISIBILITY] = $this->getVisibility(null); + if (is_array(current($metadata)) === false) { + $metadata = [$metadata]; + } + + foreach ($metadata as $entry) { + if (isset($entry[self::KEY_NAME]) === false){ + if(isset($entry[self::KEY_FILENAME]) === true) { + $entry[self::KEY_NAME] = $entry[self::KEY_FILENAME]; + } elseif(isset($entry[self::KEY_PATH]) === true) { + $entry[self::KEY_NAME] = $entry[self::KEY_PATH]; + } else { + // ? + } + } + + if (isset($entry[self::KEY_TYPE]) === true) { + switch ($entry[self::KEY_TYPE]) { + case self::KEY_BLOB: + $entry[self::KEY_TYPE] = self::KEY_FILE; + break; + + case self::KEY_TREE: + $entry[self::KEY_TYPE] = self::KEY_DIRECTORY; + break; + } + } + + if (isset($entry[self::KEY_CONTENTS]) === false) { + $entry[self::KEY_CONTENTS] = false; + } + + if (isset($entry[self::KEY_STREAM]) === false) { + $entry[self::KEY_STREAM] = false; + } + + if (isset($entry[self::KEY_TIMESTAMP]) === false) { + $entry[self::KEY_TIMESTAMP] = false; + } + + if (isset($entry[self::KEY_MODE])) { + $entry[self::KEY_VISIBILITY] = $this->fooVisibility($entry[self::KEY_MODE]); + } else { + $entry[self::KEY_VISIBILITY] = false; + } + $result[] = $entry; } return $result; } - private function normalizeTree($info) + /** + * @param $name + * @return \Github\Api\ApiInterface + */ + private function fetchApi($name) { - $result = []; - - foreach ($info as $entry) { - switch ($entry[self::KEY_TYPE]) { - case self::KEY_BLOB: - $entry[self::KEY_TYPE] = self::KEY_FILE; - break; + $this->authenticate(); + return $this->client->api($name); + } - case self::KEY_TREE: - $entry[self::KEY_TYPE] = self::KEY_DIRECTORY; - break; + private function authenticate() + { + static $hasRun; + + if ($hasRun === null) { + if (empty($this->credentials) === false) { + $credentials = array_replace( + [null, null, null], + $this->credentials + ); + + $this->client->authenticate( + $credentials[1], + $credentials[2], + $credentials[0] + ); } + $hasRun = true; + } + } - $entry[self::KEY_CONTENTS] = false; - $entry[self::KEY_STREAM] = false; - $entry[self::KEY_TIMESTAMP] = false; - //@CHECKME: Should this be the same for the entire repo or the file 'mode' - $entry[self::KEY_VISIBILITY] = $this->getVisibility(null); - $result[] = $entry; + private function fooVisibility($permissions) + { + return $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + } + + private function getPathFromTree($metadata, $path, $recursive) + { + if (empty($path)) { + if ($recursive === false) { + $metadata = array_filter($metadata, function ($entry) use ($path) { + return (strpos($entry[self::KEY_PATH], '/', strlen($path)) === false); + }); + } + } else { + $metadata = array_filter($metadata, function ($entry) use ($path, $recursive) { + $match = false; + + if (strpos($entry[self::KEY_PATH], $path) === 0) { + if ($recursive === true) { + $match = true; + } else { + $length = strlen($path); + $match = (strpos($entry[self::KEY_PATH], '/', $length) === false); + } + } + + return $match; + }); } + return $metadata; + } + + private function internalMetadata($path, $recursive) + { + // If $info['truncated'] is `true`, the number of items in the tree array + // exceeded the github maximum limit. If you need to fetch more items, + // multiple calls will be needed + + $info = $this->trees($recursive); + $tree = $this->getPathFromTree($info, $path, $recursive); + $result = $this->normalizeMetadata($tree); + return $result; } } + +/*EOF*/ From 91821bc7037c17cb742348b39999f245465ac0b0 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Mon, 25 May 2015 19:02:57 +0200 Subject: [PATCH 10/15] Catches exception for non-existent file in GithubAdapter::getSize(). --- src/GithubAdapter.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php index 4fe8823..5815d7e 100644 --- a/src/GithubAdapter.php +++ b/src/GithubAdapter.php @@ -5,6 +5,7 @@ use Github\Api\GitData; use Github\Api\Repo; use Github\Client; +use Github\Exception\RuntimeException; use League\Flysystem\Adapter\AbstractAdapter; use League\Flysystem\Adapter\Polyfill\StreamedTrait; use League\Flysystem\AdapterInterface; @@ -19,6 +20,7 @@ */ class GithubAdapter extends AbstractAdapter { + const NOT_FOUND_ERROR = 'Not Found'; use StreamedTrait; /* @@ -333,7 +335,16 @@ public function getMetadata($path) */ public function getSize($path) { - return $this->getMetadata($path); + try { + $metadata = $this->getMetadata($path); + } catch (RuntimeException $exception) { + if ($exception->getMessage() === self::NOT_FOUND_ERROR) { + $metadata = false; + } else { + throw $exception; + } + } + return $metadata; } /** From 8c14b13dcdfe33556051f74ab7c056be6c7cbc7f Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Fri, 17 Jul 2015 23:57:15 +0200 Subject: [PATCH 11/15] Added a Settings class Offers a simple object to hold all the configurable values that the GithubAdapter might need. --- src/Settings.php | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/Settings.php diff --git a/src/Settings.php b/src/Settings.php new file mode 100644 index 0000000..706eb28 --- /dev/null +++ b/src/Settings.php @@ -0,0 +1,70 @@ +branch = $branch; + $this->credentials = $credentials; + $this->reference = $reference; + $this->repository = $repository; + } + + + /** + * @return string + */ + final public function getRepository() + { + return $this->repository; + } + + /** + * @return string + */ + final public function getReference() + { + return $this->reference; + } + + /** + * @return array + */ + final public function getCredentials() + { + return $this->credentials; + } + + /** + * @return string + */ + final public function getBranch() + { + return $this->branch; + } +} + +/*EOF*/ From 5094a1955e7ae28dca6aeb44fc2c7f3a460fc670 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Sat, 18 Jul 2015 00:00:49 +0200 Subject: [PATCH 12/15] Added a Client class as a wrapper for the Github API package, to be used by the Adapter Logic moved over from the Adapter class. --- src/Client.php | 370 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 src/Client.php diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..c0c1aaf --- /dev/null +++ b/src/Client.php @@ -0,0 +1,370 @@ +client = $client; + $this->settings = $settings; + + /* @NOTE: If $settings contains `credentials` but not an `author` we are + * still in `read-only` mode. + */ + list($this->vendor, $this->package) = explode('/', $this->settings->getRepository()); + } + + /** + * @param string $path + * + * @return bool + */ + final public function exists($path) + { + return $this->repositoryContents()->exists( + $this->vendor, + $this->package, + $path, + $this->settings->getReference() + ); + } + + /** + * @param $path + * + * @return null|string + * + * @throws \Github\Exception\ErrorException + */ + final public function download($path) + { + $fileContent = $this->repositoryContents()->download( + $this->vendor, + $this->package, + $path, + $this->settings->getReference() + ); + + return $fileContent; + } + + /** + * @param string $path + * @param bool $recursive + * + * @return array + */ + final public function metadata($path, $recursive) + { + // If $info['truncated'] is `true`, the number of items in the tree array + // exceeded the github maximum limit. If you need to fetch more items, + // multiple calls will be needed + + $info = $this->trees($recursive); + $tree = $this->getPathFromTree($info, $path, $recursive); + $result = $this->normalizeMetadata($tree); + + return $result; + } + + /** + * @param string $path + * + * @return array + */ + final public function show($path) + { + // Get information about a repository file or directory + $fileInfo = $this->repositoryContents()->show( + $this->vendor, + $this->package, + $path, + $this->settings->getReference() + ); + return $fileInfo; + } + + /** + * @param string $path + * + * @return array|bool + */ + final public function getMetaData($path) + { + try { + $metadata = $this->show($path); + } catch (RuntimeException $exception) { + if ($exception->getMessage() === self::ERROR_NOT_FOUND) { + $metadata = false; + } else { + throw $exception; + } + } + + return $metadata; + } + + /** + * @param string $path + * + * @return null|string + */ + final public function guessMimeType($path) + { + //@NOTE: The github API does not return a MIME type, so we have to guess :-( + if (strrpos($path, '.') > 1) { + $extension = substr($path, strrpos($path, '.')+1); + } + + if (isset($extension)) { + $mimeType = MimeType::detectByFileExtension($extension) ?: 'text/plain'; + } else { + $content = $this->download($path); + $mimeType = MimeType::detectByContent($content); + } + + return $mimeType; + } + + /** + * @param string $path + * + * @return array + */ + final public function updated($path) + { + // List commits for a file + $commits = $this->repository()->commits()->all( + $this->vendor, + $this->package, + array( + 'sha' => $this->settings->getBranch(), + 'path' => $path + ) + ); + + $updated = array_shift($commits); + //@NOTE: $created = array_pop($commits); + + $time = new \DateTime($updated['commit']['committer']['date']); + + return ['timestamp' => $time->getTimestamp()]; + } + + /** + * @return \Github\Api\Repository\Contents + */ + private function repositoryContents() + { + return $this->repository()->contents(); + } + + /** + * + */ + private function authenticate() + { + static $hasRun; + + if ($hasRun === null) { + if (empty($this->settings->getCredentials()) === false) { + $credentials = array_replace( + [null, null, null], + $this->settings->getCredentials() + ); + + $this->client->authenticate( + $credentials[1], + $credentials[2], + $credentials[0] + ); + } + $hasRun = true; + } + } + + /** + * @return Repo + */ + private function repository() + { + return $this->fetchApi(self::KEY_REPO); + } + + /** + * @param string $name + * @return \Github\Api\ApiInterface + */ + private function fetchApi($name) + { + $this->authenticate(); + return $this->client->api($name); + } + + /** + * @param array $metadata + * @param string $path + * @param bool $recursive + * + * @return array + */ + private function getPathFromTree(array $metadata, $path, $recursive) + { + if (empty($path)) { + if ($recursive === false) { + $metadata = array_filter($metadata, function ($entry) use ($path) { + return (strpos($entry[self::KEY_PATH], '/', strlen($path)) === false); + }); + } + } else { + $metadata = array_filter($metadata, function ($entry) use ($path, $recursive) { + $match = false; + + if (strpos($entry[self::KEY_PATH], $path) === 0) { + if ($recursive === true) { + $match = true; + } else { + $length = strlen($path); + $match = (strpos($entry[self::KEY_PATH], '/', $length) === false); + } + } + + return $match; + }); + } + + return $metadata; + } + + /** + * @param array $metadata + * + * @return array + */ + private function normalizeMetadata($metadata) + { + $result = []; + + if (is_array(current($metadata)) === false) { + $metadata = [$metadata]; + } + + foreach ($metadata as $entry) { + if (isset($entry[self::KEY_NAME]) === false){ + if(isset($entry[self::KEY_FILENAME]) === true) { + $entry[self::KEY_NAME] = $entry[self::KEY_FILENAME]; + } elseif(isset($entry[self::KEY_PATH]) === true) { + $entry[self::KEY_NAME] = $entry[self::KEY_PATH]; + } else { + // ? + } + } + + if (isset($entry[self::KEY_TYPE]) === true) { + switch ($entry[self::KEY_TYPE]) { + case self::KEY_BLOB: + $entry[self::KEY_TYPE] = self::KEY_FILE; + break; + + case self::KEY_TREE: + $entry[self::KEY_TYPE] = self::KEY_DIRECTORY; + break; + } + } + + if (isset($entry[self::KEY_CONTENTS]) === false) { + $entry[self::KEY_CONTENTS] = false; + } + + if (isset($entry[self::KEY_STREAM]) === false) { + $entry[self::KEY_STREAM] = false; + } + + if (isset($entry[self::KEY_TIMESTAMP]) === false) { + $entry[self::KEY_TIMESTAMP] = false; + } + + if (isset($entry[self::KEY_MODE])) { + $entry[self::KEY_VISIBILITY] = $this->visibility($entry[self::KEY_MODE]); + } else { + $entry[self::KEY_VISIBILITY] = false; + } + + $result[] = $entry; + } + + return $result; + } + + /** + * @return GitData + */ + private function gitData() + { + return $this->fetchApi(self::KEY_GIT_DATA); + } + + /** + * @param bool $recursive + * @return \Guzzle\Http\EntityBodyInterface|mixed|string + */ + private function trees($recursive) + { + $trees = $this->gitData()->trees(); + + $info = $trees->show( + $this->vendor, + $this->package, + $this->settings->getReference(), + $recursive + ); + + return $info[self::KEY_TREE]; + } + + /** + * @param $permissions + * @return string + */ + private function visibility($permissions) + { + return $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + } +} From 64c1f0312e357c89194c4bcaa0020ff1ad99c059 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Sat, 18 Jul 2015 00:20:50 +0200 Subject: [PATCH 13/15] Changed GithubAdapter to use logic from the Client. --- src/GithubAdapter.php | 439 +++--------------------------------------- 1 file changed, 28 insertions(+), 411 deletions(-) diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php index 5815d7e..c4e5d2a 100644 --- a/src/GithubAdapter.php +++ b/src/GithubAdapter.php @@ -2,50 +2,19 @@ namespace Potherca\Flysystem\Github; -use Github\Api\GitData; -use Github\Api\Repo; -use Github\Client; -use Github\Exception\RuntimeException; use League\Flysystem\Adapter\AbstractAdapter; use League\Flysystem\Adapter\Polyfill\StreamedTrait; -use League\Flysystem\AdapterInterface; use League\Flysystem\Config; +use League\Flysystem\Exception; use League\Flysystem\Util; - /** - * @FIXME: Once the functionality is clear, all of the Github Client calls need to be moved - * to Potherca\Flysystem\GithubClient which is a composite of the Github\Api\* and - * the Github\Client classes. + * */ class GithubAdapter extends AbstractAdapter { - const NOT_FOUND_ERROR = 'Not Found'; use StreamedTrait; - /* - stream stream (resource) - */ - const KEY_BLOB = 'blob'; - const KEY_CONTENTS = 'contents'; - const KEY_DIRECTORY = 'dir'; - const KEY_FILE = 'file'; - const KEY_FILENAME = 'basename'; - const KEY_GIT_DATA = 'git'; - const KEY_MODE = 'mode'; - const KEY_NAME = 'name'; - const KEY_PATH = 'path'; - const KEY_REPO = 'repo'; - const KEY_SHA = 'sha'; - const KEY_SIZE = 'size'; - const KEY_STREAM = 'stream'; - const KEY_TIMESTAMP = 'timestamp'; - const KEY_TREE = 'tree'; - const KEY_TYPE = 'type'; - const KEY_VISIBILITY = 'visibility'; - - const BRANCH_MASTER = 'master'; - const COMMITTER_MAIL = 'email'; const COMMITTER_NAME = 'name'; @@ -53,40 +22,14 @@ class GithubAdapter extends AbstractAdapter const VISIBILITY_PUBLIC = 'public'; /** @var Client */ - protected $client; - /** @var string */ - private $branch = self::BRANCH_MASTER; - private $commitMessage; - private $committerEmail; - private $committerName; - private $package; - private $reference; - private $vendor; - private $committer = array( - self::COMMITTER_NAME => null, - self::COMMITTER_MAIL => null, - ); - private $credentials = []; + private $client; /** * @param Client $client - * @param Settings $settings */ - public function __construct( - Client $client, - Settings $settings - ) { + public function __construct(Client $client) + { $this->client = $client; - - /* @NOTE: If $client contains `credentials` but not an `author` we are - * still in `read-only` mode. - */ - //@CHECKME Is it really necessary that we man-handle each setting? - // Can't we just use the settings object directly instead? - $this->repository = $settings->repository; - $this->reference = $settings->reference; - list($this->vendor, $this->package) = explode('/', $this->repository); - $this->credentials = $settings->credentials; } /** @@ -100,32 +43,8 @@ public function __construct( */ public function write($path, $contents, Config $config) { - // Create a file - $fileInfo = $this->repositoryContents()->create( - $this->vendor, - $this->package, - $path, - $contents, - $this->commitMessage, - $this->branch, - $this->committer - ); - - return $fileInfo; - } - - /** - * Write a new file using a stream. - * - * @param string $path - * @param resource $resource - * @param Config $config Config object - * - * @return array|false false on failure file meta data on success - */ - public function writeStream($path, $resource, Config $config) - { - // @TODO: Use writeStream() trait, once write() has been implemented + throw new Exception('Write action are not (yet) supported'); + //@TODO: return $this->client->create($path, $contents); } /** @@ -139,34 +58,8 @@ public function writeStream($path, $resource, Config $config) */ public function update($path, $contents, Config $config) { - $oldFile = $this->getMetadata($path); - - $fileInfo = $this->repositoryContents()->update( - $this->vendor, - $this->package, - $path, - $contents, - $this->commitMessage, - $oldFile[self::KEY_SHA], - $this->branch, - $this->committer - ); - - return $fileInfo; - } - - /** - * Update a file using a stream. - * - * @param string $path - * @param resource $resource - * @param Config $config Config object - * - * @return array|false false on failure file meta data on success - */ - public function updateStream($path, $resource, Config $config) - { - // TODO: Implement updateStream() method. + throw new Exception('Write action are not (yet) supported'); + // @TODO: return $this->client->update($path, $contents); } /** @@ -179,7 +72,8 @@ public function updateStream($path, $resource, Config $config) */ public function rename($path, $newpath) { - // TODO: Implement rename() method. + throw new Exception('Write action are not (yet) supported'); + // @TODO: return $this->client->rename($path, $newPath); } /** @@ -192,7 +86,8 @@ public function rename($path, $newpath) */ public function copy($path, $newpath) { - // TODO: Implement copy() method. + throw new Exception('Write action are not (yet) supported'); + // @TODO: return $this->client->copy($path, $newPath); } /** @@ -204,19 +99,8 @@ public function copy($path, $newpath) */ public function delete($path) { - $oldFile = $this->getMetadata($path); - - $fileInfo = $this->repositoryContents()->rm( - $this->vendor, - $this->package, - $path, - $this->commitMessage, - $oldFile['sha'], - $this->branch, - $this->committer - ); - - return $fileInfo; + throw new Exception('Write action are not (yet) supported'); + // @TODO: return $this->client->delete($path); } /** @@ -228,7 +112,8 @@ public function delete($path) */ public function deleteDir($dirname) { - // TODO: Implement deleteDir() method. + throw new Exception('Write action are not (yet) supported'); + // @TODO: return $this->client->deleteDir($dirname); } /** @@ -241,7 +126,8 @@ public function deleteDir($dirname) */ public function createDir($dirname, Config $config) { - // TODO: Implement createDir() method. + throw new Exception('Write action are not (yet) supported'); + // @TODO: return $this->client->createDir($dirname); } /** @@ -266,12 +152,7 @@ public function setVisibility($path, $visibility) */ public function has($path) { - return $this->repositoryContents()->exists( - $this->vendor, - $this->package, - $path, - $this->reference - ); + return $this->client->exists($path); } /** @@ -283,14 +164,7 @@ public function has($path) */ public function read($path) { - $fileContent = $this->repositoryContents()->download( - $this->vendor, - $this->package, - $path, - $this->reference - ); - - return [self::KEY_CONTENTS => $fileContent]; + return [Client::KEY_CONTENTS => $this->client->download($path)]; } /** @@ -303,7 +177,7 @@ public function read($path) */ public function listContents($path = '/', $recursive = false) { - return $this->internalMetadata($path, $recursive); + return $this->client->metadata($path, $recursive); } /** @@ -315,15 +189,7 @@ public function listContents($path = '/', $recursive = false) */ public function getMetadata($path) { - // Get information about a repository file or directory - $fileInfo = $this->repositoryContents()->show( - $this->vendor, - $this->package, - $path, - $this->reference - ); - - return $fileInfo; + return $this->client->show($path); } /** @@ -335,16 +201,7 @@ public function getMetadata($path) */ public function getSize($path) { - try { - $metadata = $this->getMetadata($path); - } catch (RuntimeException $exception) { - if ($exception->getMessage() === self::NOT_FOUND_ERROR) { - $metadata = false; - } else { - throw $exception; - } - } - return $metadata; + return $this->client->getMetaData($path); } /** @@ -356,19 +213,7 @@ public function getSize($path) */ public function getMimetype($path) { - //@NOTE: The github API does not return a MIME type, so we have to guess :-( - if (strrpos($path, '.') > 1) { - $extension = substr($path, strrpos($path, '.')+1); - } - - if (isset($extension)) { - $mimeType = Util\MimeType::detectByFileExtension($extension) ?: 'text/plain'; - } else { - $content = $this->read($path); - $mimeType = Util\MimeType::detectByContent($content['contents']); - } - - return ['mimetype' => $mimeType]; + return ['mimetype' => $this->client->guessMimeType($path)]; } /** @@ -380,21 +225,7 @@ public function getMimetype($path) */ public function getTimestamp($path) { - // List commits for a file - $commits = $this->repository()->commits()->all( - $this->vendor, - $this->package, - array( - 'sha' => $this->branch, - 'path' => $path - ) - ); - $updated = array_shift($commits); - //@NOTE: $created = array_pop($commits); - - $time = new \DateTime($updated['commit']['committer']['date']); - - return ['timestamp' => $time->getTimestamp()]; + return $this->client->updated($path); } /** @@ -406,224 +237,10 @@ public function getTimestamp($path) */ public function getVisibility($path) { - $metadata = $this->internalMetadata($path, false); + $recursive = false; + $metadata = $this->client->metadata($path, $recursive); return $metadata[0]; } - - /** - * @param string $path File or Folder path - * @param string $content The content to write to the given file - * - * @throws \Github\Exception\ErrorException - * @throws \Github\Exception\MissingArgumentException - */ - private function foo($path, $content = '') - { - $this->branch = $this->reference; - - $this->committerName = 'KnpLabs'; - $this->committerEmail = 'info@knplabs.com'; - $this->vendor = 'knp-labs'; - $this->package = 'php-github-api'; - - $this->committer = array( - self::COMMITTER_NAME => $this->committerName, - self::COMMITTER_MAIL => $this->committerEmail - ); - $this->commitMessage = 'Edited with Flysystem'; - - - // https://github.com/thephpleague/flysystem/wiki/Adapter-Internals - /*********** Meta Data Values ********** - ------------------------------------- - key | description - ------------------------------------- - type | file or dir - path | path to the file or dir - contents | file contents (string) - stream | stream (resource) - visibility | public or private - timestamp | modified time - ------------------------------------- - - When an adapter can not provide the metadata with the key that's required to satisfy the call, false should be returned. - - */ - } - - /** - * @return \Github\Api\Repository\Contents - */ - private function repositoryContents() - { - return $this->repository()->contents(); - } - - /** - * @return Repo - */ - private function repository() - { - return $this->fetchApi(self::KEY_REPO); - } - - /** - * @return GitData - */ - private function gitData() - { - return $this->fetchApi(self::KEY_GIT_DATA); - } - - /** - * @param $recursive - * @return \Guzzle\Http\EntityBodyInterface|mixed|string - */ - private function trees($recursive) - { - $trees = $this->gitData()->trees(); - - $info = $trees->show( - $this->vendor, - $this->package, - $this->reference, - $recursive - ); - - return $info[self::KEY_TREE]; - } - - private function normalizeMetadata($metadata) - { - $result = []; - - if (is_array(current($metadata)) === false) { - $metadata = [$metadata]; - } - - foreach ($metadata as $entry) { - if (isset($entry[self::KEY_NAME]) === false){ - if(isset($entry[self::KEY_FILENAME]) === true) { - $entry[self::KEY_NAME] = $entry[self::KEY_FILENAME]; - } elseif(isset($entry[self::KEY_PATH]) === true) { - $entry[self::KEY_NAME] = $entry[self::KEY_PATH]; - } else { - // ? - } - } - - if (isset($entry[self::KEY_TYPE]) === true) { - switch ($entry[self::KEY_TYPE]) { - case self::KEY_BLOB: - $entry[self::KEY_TYPE] = self::KEY_FILE; - break; - - case self::KEY_TREE: - $entry[self::KEY_TYPE] = self::KEY_DIRECTORY; - break; - } - } - - if (isset($entry[self::KEY_CONTENTS]) === false) { - $entry[self::KEY_CONTENTS] = false; - } - - if (isset($entry[self::KEY_STREAM]) === false) { - $entry[self::KEY_STREAM] = false; - } - - if (isset($entry[self::KEY_TIMESTAMP]) === false) { - $entry[self::KEY_TIMESTAMP] = false; - } - - if (isset($entry[self::KEY_MODE])) { - $entry[self::KEY_VISIBILITY] = $this->fooVisibility($entry[self::KEY_MODE]); - } else { - $entry[self::KEY_VISIBILITY] = false; - } - - $result[] = $entry; - } - - return $result; - } - - /** - * @param $name - * @return \Github\Api\ApiInterface - */ - private function fetchApi($name) - { - $this->authenticate(); - return $this->client->api($name); - } - - private function authenticate() - { - static $hasRun; - - if ($hasRun === null) { - if (empty($this->credentials) === false) { - $credentials = array_replace( - [null, null, null], - $this->credentials - ); - - $this->client->authenticate( - $credentials[1], - $credentials[2], - $credentials[0] - ); - } - $hasRun = true; - } - } - - private function fooVisibility($permissions) - { - return $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; - } - - private function getPathFromTree($metadata, $path, $recursive) - { - if (empty($path)) { - if ($recursive === false) { - $metadata = array_filter($metadata, function ($entry) use ($path) { - return (strpos($entry[self::KEY_PATH], '/', strlen($path)) === false); - }); - } - } else { - $metadata = array_filter($metadata, function ($entry) use ($path, $recursive) { - $match = false; - - if (strpos($entry[self::KEY_PATH], $path) === 0) { - if ($recursive === true) { - $match = true; - } else { - $length = strlen($path); - $match = (strpos($entry[self::KEY_PATH], '/', $length) === false); - } - } - - return $match; - }); - } - - return $metadata; - } - - private function internalMetadata($path, $recursive) - { - // If $info['truncated'] is `true`, the number of items in the tree array - // exceeded the github maximum limit. If you need to fetch more items, - // multiple calls will be needed - - $info = $this->trees($recursive); - $tree = $this->getPathFromTree($info, $path, $recursive); - $result = $this->normalizeMetadata($tree); - - return $result; - } } /*EOF*/ From ab92634084e793f3dbf5a37428ee765ded552eb0 Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Sat, 18 Jul 2015 00:53:21 +0200 Subject: [PATCH 14/15] Changes CHANGELOG file to include latest changes. --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..576590e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log +All notable changes to the `flysystem-github` project will be documented in this +file. This project adheres to [Semantic Versioning](http://semver.org/). + +## 0.1.0 - 2015-07-18 - Read functionality +### Added +Read functionality and Github API authentication have been implemented. + +## 0.0.0 - 2015-05-11 - Project Setup +### Added +Set up project basics like .gitignore file, PHPUnit Configuration file, +Contributing guidelines, Composer file stating dependencies, MIT License, README +file and this CHANGELOG file. + +[unreleased]: https://github.com/potherca/flystystem-github/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/potherca/flystystem-github/compare/v0.0.0...v0.1.0 From 58c8bc950448ce04673e1190cd715a36e2edc94a Mon Sep 17 00:00:00 2001 From: Ben Peachey Date: Sat, 18 Jul 2015 00:54:10 +0200 Subject: [PATCH 15/15] Changed README file to reflect changes to the class API. --- README.md | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a2a6461..bc81427 100644 --- a/README.md +++ b/README.md @@ -19,64 +19,73 @@ $ composer require potherca/flysystem-github ## Usage The Github adapter can be used *without* credentials to do read-only actions on -public repositories. To save changes or read from private repositories, -credentials are required. +public repositories. To avoid reaching the Github API limit, to save changes, or +to read from private repositories, credentials are required. -To avoid the Github API limit or to save traffic caching can be utilized. +Caching can be utilized to save traffic or to postpone reaching the Github API +limit. ### Basic Usage ```php -use Github\Client; +use Github\Client as GithubClient; use League\Flysystem\Filesystem; +use Potherca\Flysystem\Github\Client; use Potherca\Flysystem\Github\GithubAdapter; +use Potherca\Flysystem\Github\Settings; $project = 'thephpleague/flysystem'; -$client = new Client(); -$adapter = new GithubAdapter($client, $project); +$settings = new Settings($project); +$client = new Client(new GithubClient(), $settings); +$adapter = new GithubAdapter($client); $filesystem = new Filesystem($adapter); ``` ### Authentication ```php -use Github\Client; +use Github\Client as GithubClient; use League\Flysystem\Filesystem; +use Potherca\Flysystem\Github\Client; use Potherca\Flysystem\Github\GithubAdapter; +use Potherca\Flysystem\Github\Settings; $project = 'thephpleague/flysystem'; +$credentials = [Settings::AUTHENTICATE_USING_TOKEN, '83347e315b8bb4790a48ed6953a5ad9e825b4e10']; +// or $authentications = [Settings::AUTHENTICATE_USING_PASSWORD, $username, $password]; + +$settings = new Settings($project, $credentials); -$client = new Client(); -$client->authenticate($token, null, Client::AUTH_HTTP_TOKEN); -// or $client->authenticate($username, $password, Client::AUTH_HTTP_PASSWORD); -$adapter = new GithubAdapter($client, $project); - +$client = new Client(new GithubClient(), $settings); +$adapter = new GithubAdapter($client); $filesystem = new Filesystem($adapter); ``` -For full details, see the [php-github-api documentation concerning authentication](https://github.com/KnpLabs/php-github-api/blob/master/doc/security.md). - ### Cache Usage ```php -use Github\Client; +use Github\Client as GithubClient; use Github\HttpClient\CachedHttpClient as CachedClient; use Github\HttpClient\Cache\FilesystemCache as Cache; use League\Flysystem\Filesystem; +use Potherca\Flysystem\Github\Client; use Potherca\Flysystem\Github\GithubAdapter; +use Potherca\Flysystem\Github\Settings; $project = 'thephpleague/flysystem'; +$settings = new Settings($project); + $cache = new Cache('/tmp/github-api-cache') $cacheClient = new CachedClient(); $cacheClient->setCache($cache); -$client = new Client($cacheClient); -$adapter = new GithubAdapter($client, $project); - +$client = new Client($cacheClient, $settings); +$adapter = new GithubAdapter($client); $filesystem = new Filesystem($adapter); + ``` ## Testing