From 524445c3466b43f44eeb8e34b9931a05dddb104f Mon Sep 17 00:00:00 2001 From: Cameron Ball Date: Wed, 3 Dec 2014 14:32:18 +0800 Subject: [PATCH] Quota tracking now works. Quota currently defaults to 100 for each user and resets at 00:00 UTC+0 each day. --- Controllers/DownloadTestController.php | 22 +++----- Controllers/FileController.php | 61 +++++++++++++++++++++- Controllers/SimfileController.php | 9 ++-- Controllers/UserAuthController.php | 1 + Controllers/UserController.php | 11 +++- DataAccess/DataMapper/DataMapper.php | 1 - .../Helpers/AbstractPopulationHelper.php | 3 ++ Domain/Entities/IUser.php | 1 + Domain/Entities/IUserBuilder.php | 1 + Domain/Entities/User.php | 13 ++++- Domain/Entities/UserBuilder.php | 9 +++- Domain/Entities/UserFactory.php | 9 ++-- Domain/Entities/UserStepByStepBuilder.php | 14 +++++ Domain/Util.php | 11 ++++ Services/Http/HttpResponse.php | 12 +++++ Services/Http/IHttpResponse.php | 1 + Services/IUserQuota.php | 8 +++ Services/IUserSession.php | 1 - Services/UserQuota.php | 51 ++++++++++++++++++ Services/UserSession.php | 6 +-- config/DI.php | 3 ++ config/DataMaps.php | 3 +- config/Routes.php | 6 +++ 23 files changed, 221 insertions(+), 36 deletions(-) create mode 100644 Services/IUserQuota.php create mode 100644 Services/UserQuota.php diff --git a/Controllers/DownloadTestController.php b/Controllers/DownloadTestController.php index c12df99..1f92ab4 100644 --- a/Controllers/DownloadTestController.php +++ b/Controllers/DownloadTestController.php @@ -3,30 +3,20 @@ namespace Controllers; use Controllers\IDivineController; -use DataAccess\IDownloadRepository; -use DataAccess\Queries\DownloadQueryConstraints; -use DateTime; +use Services\IUserQuota; class DownloadTestController implements IDivineController { - private $_downloadRepository; + private $_quotaManager; public function __construct( - IDownloadRepository $repository + IUserQuota $quotaManager ) { - $this->_downloadRepository = $repository; + $this->_quotaManager = $quotaManager; } public function indexAction() { - $start = new DateTime('0:00 today'); - $end = new DateTime(); - - $constraints = new DownloadQueryConstraints(); - $constraints->inDateRange($start, $end); - $downloads = $this->_downloadRepository->findByUserId(4, $constraints); - - echo '
';
-        print_r($downloads);
-        echo '
'; + $quota = (($this->_quotaManager->getCurrentUserQuotaRemaining())/1000)/1000; + echo $quota; } } diff --git a/Controllers/FileController.php b/Controllers/FileController.php index 4b627db..1fb1cb2 100644 --- a/Controllers/FileController.php +++ b/Controllers/FileController.php @@ -5,22 +5,38 @@ namespace Controllers; use Controllers\IDivineController; use Services\Http\IHttpRequest; use Services\Http\IHttpResponse; +use Services\IUserSession; +use Services\IUserQuota; +use Domain\Entities\IDownloadFactory; use DataAccess\IFileRepository; +use DataAccess\IDownloadRepository; class FileController implements IDivineController { private $_fileRepository; private $_response; private $_request; + private $_downloadRepository; + private $_downloadFactory; + private $_userSession; + private $_userQuota; public function __construct( IHttpRequest $request, IHttpResponse $response, - IFileRepository $repository + IFileRepository $repository, + IDownloadFactory $downloadFactory, + IDownloadRepository $downloadRepository, + IUserSession $userSession, + IUserQuota $userQuota ) { $this->_request = $request; $this->_response = $response; $this->_fileRepository = $repository; + $this->_downloadRepository = $downloadRepository; + $this->_downloadFactory = $downloadFactory; + $this->_userSession = $userSession; + $this->_userQuota = $userQuota; } public function indexAction() { @@ -52,6 +68,33 @@ class FileController implements IDivineController exit(); } + public function servePackAction($hash) + { + $file = $this->_fileRepository->findByHash($hash); + $quotaRemaining = $this->_userQuota->getCurrentUserQuotaRemaining(); + + if(!$file) $this->notFound(); + if(!$quotaRemaining) $this->notAuthorised(); + if($quotaRemaining < $file->getSize()) $this->notEnoughQuota(); + + // TODO: Builder? + $download = $this->_downloadFactory->createInstance($this->_userSession->getCurrentUser(), + $file, + time(), + $this->_request->getIp()); + + $this->_downloadRepository->save($download); + + $zip = '../files/' . $file->getPath() . '/' . $file->getHash() . '.zip'; + //TODO: This may not work on all browser or something. We'll have to see. Also it may hog ram so... + $this->_response->setHeader('Content-Type', $file->getMimetype()) + ->setHeader('Content-Length', $file->getSize()) + ->setHeader('Content-Disposition', 'filename="' . $file->getFileName() . '";') + ->setHeader('Content-Transfer-Encoding', 'binary') + ->setBody(file_get_contents($zip)) + ->sendResponse(); + } + private function notFound() { $this->_response->setHeader('HTTP/1.0 404 Not Found', 'Nothing to see here') @@ -59,4 +102,20 @@ class FileController implements IDivineController ->sendResponse(); exit(); } + + private function notAuthorised() + { + $this->_response->setHeader('Content-Type', 'application/json') + ->setBody(json_encode(array('error' => 'You must be authenticated to download files'))) + ->sendResponse(); + exit(); + } + + private function notEnoughQuota() + { + $this->_response->setHeader('Content-Type', 'application/json') + ->setBody(json_encode(array('error' => 'You don\'t have enough quota remaining for this file.'))) + ->sendResponse(); + exit(); + } } diff --git a/Controllers/SimfileController.php b/Controllers/SimfileController.php index bff9689..2763fd6 100644 --- a/Controllers/SimfileController.php +++ b/Controllers/SimfileController.php @@ -13,7 +13,6 @@ use DataAccess\StepMania\IPackRepository; use DataAccess\IFileRepository; use Domain\Entities\StepMania\ISimfile; use Domain\Entities\IFile; -use Domain\VOs\IFileMirror; class SimfileController implements IDivineController { @@ -22,7 +21,6 @@ class SimfileController implements IDivineController private $_fileRepository; private $_response; private $_uploadManager; - private $_userSession; private $_zipParser; private $_smoMatcher; @@ -41,7 +39,6 @@ class SimfileController implements IDivineController $this->_simfileRepository = $simfileRepository; $this->_packRepository = $packRepository; $this->_fileRepository = $fileRepository; - $this->_userSession = $userSession; $this->_zipParser = $zipParser; $this->_smoMatcher = $smoMatcher; } @@ -73,6 +70,12 @@ class SimfileController implements IDivineController } $packMirrors = array(); + + if($pack->getFile()) + { + $packMirrors[] = array('source' => 'DivinElegy', 'uri' => 'files/pack/' . $pack->getFile()->getHash()); + } + if($pack->getFile()->getMirrors()) { foreach($pack->getFile()->getMirrors() as $mirror) diff --git a/Controllers/UserAuthController.php b/Controllers/UserAuthController.php index c85988a..0f044d1 100644 --- a/Controllers/UserAuthController.php +++ b/Controllers/UserAuthController.php @@ -93,6 +93,7 @@ class UserAuthController implements IDivineController ->With_Name(new \Domain\VOs\Name($firstName, $lastName)) ->With_Tags(array()) ->With_FacebookId($facebookId) + ->With_Quota(100000000) //XXX: quota is in bytes ->build(); $this->_userRepository->save($newUser); diff --git a/Controllers/UserController.php b/Controllers/UserController.php index 822dd13..62d150a 100644 --- a/Controllers/UserController.php +++ b/Controllers/UserController.php @@ -5,22 +5,27 @@ namespace Controllers; use Controllers\IDivineController; use Services\Http\IHttpRequest; use Services\Http\IHttpResponse; +use Services\IUserQuota; use DataAccess\IUserRepository; +use Domain\Util; class UserController implements IDivineController { private $_userRepository; private $_response; private $_request; + private $_userQuota; public function __construct( IHttpRequest $request, IHttpResponse $response, - IUserRepository $userRepository + IUserRepository $userRepository, + IUserQuota $userQuota ) { $this->_request = $request; $this->_response = $response; $this->_userRepository = $userRepository; + $this->_userQuota = $userQuota; } public function indexAction() { @@ -38,7 +43,9 @@ class UserController implements IDivineController 'name' => $user->getName()->getFullName(), 'displayName' => $user->getDisplayName(), 'tags' => $user->getTags(), - 'country' => $user->getCountry()->getCountryName() + 'country' => $user->getCountry()->getCountryName(), + 'quota' => Util::bytesToHumanReadable($user->getQuota()), + 'quotaRemaining' => Util::bytesToHumanReadable($this->_userQuota->getCurrentUserQuotaRemaining()) ); $this->_response->setHeader('Content-Type', 'application/json') diff --git a/DataAccess/DataMapper/DataMapper.php b/DataAccess/DataMapper/DataMapper.php index fd9c242..d5c3d9d 100644 --- a/DataAccess/DataMapper/DataMapper.php +++ b/DataAccess/DataMapper/DataMapper.php @@ -23,7 +23,6 @@ class DataMapper implements IDataMapper public function map($entityName, IQueryBuilder $queryBuilder) { $queryString = $queryBuilder->buildQuery(); - $statement = $this->_db->prepare(sprintf($queryString, $this->_maps[$entityName]['table'] )); diff --git a/DataAccess/DataMapper/Helpers/AbstractPopulationHelper.php b/DataAccess/DataMapper/Helpers/AbstractPopulationHelper.php index d32f464..eab5154 100644 --- a/DataAccess/DataMapper/Helpers/AbstractPopulationHelper.php +++ b/DataAccess/DataMapper/Helpers/AbstractPopulationHelper.php @@ -22,6 +22,9 @@ class AbstractPopulationHelper switch(get_class($mapsHelper)) { case 'DataAccess\DataMapper\Helpers\IntMapsHelper': + if(!empty($row[$mapsHelper->getColumnName()]) && (string)(int)$row[$mapsHelper->getColumnName()] != $row[$mapsHelper->getColumnName()]) throw new Exception('Expected numeric value.'); + $constructors[$constructor] = (int)$row[$mapsHelper->getColumnName()]; + break; case 'DataAccess\DataMapper\Helpers\VarcharMapsHelper': $constructors[$constructor] = $row[$mapsHelper->getColumnName()]; break; diff --git a/Domain/Entities/IUser.php b/Domain/Entities/IUser.php index aefd961..01314bf 100644 --- a/Domain/Entities/IUser.php +++ b/Domain/Entities/IUser.php @@ -13,4 +13,5 @@ interface IUser public function getYearsStepArtist(); public function getFacebookId(); public function setFacebookId($id); + public function getQuota(); } diff --git a/Domain/Entities/IUserBuilder.php b/Domain/Entities/IUserBuilder.php index a938f0e..bb5ca03 100644 --- a/Domain/Entities/IUserBuilder.php +++ b/Domain/Entities/IUserBuilder.php @@ -13,5 +13,6 @@ interface IUserBuilder public function With_Tags(array $tags); public function With_FacebookId($id); public function With_YearsStepArtist($years); + public function With_Quota($quota); public function build(); } diff --git a/Domain/Entities/User.php b/Domain/Entities/User.php index dfa67e9..627d682 100644 --- a/Domain/Entities/User.php +++ b/Domain/Entities/User.php @@ -15,19 +15,22 @@ class User extends AbstractEntity implements IUser private $_tags; private $_yearsStepArtist; private $_facebookId; + private $_quota; public function __construct( ICountry $country, $displayName, IName $name, array $tags, - $facebookId + $facebookId, + $quota //TODO: Maybe quota should be implemented as an object? ) { $this->_country = $country; $this->_displayName = $displayName; $this->_name = $name; $this->_tags = $tags; $this->_facebookId = $facebookId; + $this->_quota = $quota; } public function getCountry() { @@ -56,7 +59,13 @@ class User extends AbstractEntity implements IUser $this->_facebookId = $id; } - public function getYearsStepArtist() { + public function getYearsStepArtist() + { return $this->_yearsStepArtist; } + + public function getQuota() + { + return $this->_quota; + } } diff --git a/Domain/Entities/UserBuilder.php b/Domain/Entities/UserBuilder.php index 8957a3d..73eeb80 100644 --- a/Domain/Entities/UserBuilder.php +++ b/Domain/Entities/UserBuilder.php @@ -16,6 +16,7 @@ class UserBuilder implements IUserBuilder private $_tags; private $_facebookId; private $_yearsStepArtist; + private $_quota; public function __construct(IUserFactory $userFactory) { @@ -52,12 +53,18 @@ class UserBuilder implements IUserBuilder return $this; } + public function With_Quota($quota) + { + $this->_quota = $quota; + } + public function build() { return $this->_userFactory ->createInstance($this->_country, $this->_displayName, $this->_name, $this->_tags, - $this->_facebookId); + $this->_facebookId, + $this->_quota); } } \ No newline at end of file diff --git a/Domain/Entities/UserFactory.php b/Domain/Entities/UserFactory.php index 94235be..f2e650d 100644 --- a/Domain/Entities/UserFactory.php +++ b/Domain/Entities/UserFactory.php @@ -13,7 +13,8 @@ interface IUserFactory $displayName, IName $name, array $tags, - $facebookId + $facebookId, + $quota ); } @@ -24,14 +25,16 @@ class UserFactory implements IUserFactory $displayName, IName $name, array $tags, - $facebookId + $facebookId, + $quota ) { return new User( $country, $displayName, $name, $tags, - $facebookId + $facebookId, + $quota ); } } \ No newline at end of file diff --git a/Domain/Entities/UserStepByStepBuilder.php b/Domain/Entities/UserStepByStepBuilder.php index e586e64..6f8d55c 100644 --- a/Domain/Entities/UserStepByStepBuilder.php +++ b/Domain/Entities/UserStepByStepBuilder.php @@ -32,6 +32,11 @@ interface IUserStepByStepBuilder_With_Tags interface IUserStepByStepBuilder_With_FacebookId { + public function With_Quota($quota); +} + +interface IUserStepByStepBuilder_With_Quota +{ public function With_YearsStepArtist($years); //not going to make this mandatory as it is kind of a joke public function build(); } @@ -88,6 +93,15 @@ class UserStepByStepBuilder_With_Tags extends AbstractUserStepByStepBuilder impl class UserStepByStepBuilder_With_FacebookId extends AbstractUserStepByStepBuilder implements IUserStepByStepBuilder_With_FacebookId { + public function With_Quota($quota) + { + $this->_userBuilder->With_Quota($quota); + return new UserStepByStepBuilder_With_Quota($this->_userBuilder); + } +} + +class UserStepByStepBuilder_With_Quota extends AbstractUserStepByStepBuilder implements IUserStepByStepBuilder_With_Quota +{ public function With_YearsStepArtist($years) { $this->_userBuilder->With_YearsStepArtist($years); return $this; diff --git a/Domain/Util.php b/Domain/Util.php index 1b0d555..df81dbb 100644 --- a/Domain/Util.php +++ b/Domain/Util.php @@ -273,4 +273,15 @@ class Util } return null; } + + public static function bytesToHumanReadable($bytes, $dec = 2) + { + if(!is_int($bytes)) throw new \Exception('Bytes must be an int.' . var_dump($bytes)); + + //Shamelessly stolen from stackoverflow: http://stackoverflow.com/questions/15188033/human-readable-file-size + $size = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); + $factor = floor((strlen($bytes) - 1) / 3); + + return sprintf("%.{$dec}f", $bytes / pow(1000, $factor)) . @$size[$factor]; + } } \ No newline at end of file diff --git a/Services/Http/HttpResponse.php b/Services/Http/HttpResponse.php index 3041556..bc6fd3a 100644 --- a/Services/Http/HttpResponse.php +++ b/Services/Http/HttpResponse.php @@ -59,6 +59,9 @@ class HttpResponse implements IHttpResponse $this->_statusCode); $statusCodeSent = true; + } else { + header( + sprintf('%s:%s', $headerName, $headerValue)); } } @@ -90,4 +93,13 @@ class HttpResponse implements IHttpResponse $this->sendHeaders() ->sendBody(); } + + public function download($path) + { + $fp = fopen($path, "rb"); + $this->sendHeaders(); + @ob_clean(); + rewind($fp); + fpassthru($fp); + } } diff --git a/Services/Http/IHttpResponse.php b/Services/Http/IHttpResponse.php index 659cb7e..4797b09 100644 --- a/Services/Http/IHttpResponse.php +++ b/Services/Http/IHttpResponse.php @@ -13,4 +13,5 @@ interface IHttpResponse public function getBody(); public function isRedirect(); public function sendResponse(); + public function download($path); } \ No newline at end of file diff --git a/Services/IUserQuota.php b/Services/IUserQuota.php new file mode 100644 index 0000000..0b29062 --- /dev/null +++ b/Services/IUserQuota.php @@ -0,0 +1,8 @@ +_userSession = $userSession; + $this->_downloadRepository = $downloadRepository; + } + + public function getCurrentUserQuotaRemaining() + { + $start = new DateTime('0:00 today'); // start of today + $end = new DateTime(); // now + $user = $this->_userSession->getCurrentUser(); + + if(!$user) return null; + + // TODO: factory? + $constraints = new DownloadQueryConstraints(); + $constraints->inDateRange($start, $end); + $downloads = $this->_downloadRepository->findByUserId($user->getId(), $constraints); + + return $user->getQuota() - $this->sumDownloads($downloads); + } + + private function sumDownloads(array $downloads) + { + $total = 0; + + foreach($downloads as $download) + { + $total += $download->getFile()->getSize(); + } + + return $total; + } +} diff --git a/Services/UserSession.php b/Services/UserSession.php index 53052d6..04505a0 100644 --- a/Services/UserSession.php +++ b/Services/UserSession.php @@ -24,11 +24,7 @@ class UserSession implements IUserSession { return $this->_currentUser; } - - public function getCurrentUserQuota() { - ; - } - + private function findToken() { if($this->_request->isPost()) diff --git a/config/DI.php b/config/DI.php index 34e6be0..5d0838b 100644 --- a/config/DI.php +++ b/config/DI.php @@ -23,6 +23,8 @@ return [ 'Domain\Entities\IFileFactory' => DI\object('Domain\Entities\FileFactory'), 'Domain\Entities\IFileBuilder' => DI\object('Domain\Entities\FileBuilder'), 'Domain\Entities\IFileStepByStepBuilder' => DI\object('Domain\Entities\FileStepByStepBuilder'), + + 'Domain\Entities\IDownloadFactory' => DI\object('Domain\Entities\DownloadFactory'), //services 'Services\Http\IHttpResponse' => DI\object('Services\Http\HttpResponse'), @@ -31,6 +33,7 @@ return [ ->constructor(DI\link('router.maps')), 'Services\Uploads\IUploadManager' => DI\object('Services\Uploads\UploadManager'), 'Services\IUserSession' => DI\object('Services\UserSession'), + 'Services\IUserQuota' => DI\object('Services\UserQuota'), 'Services\Uploads\IFileFactory' => DI\object('Services\Uploads\FileFactory'), 'Services\IFacebookSessionFactory' => DI\object('Services\FacebookSessionFactory') ->constructor(DI\link('facebook.app')), diff --git a/config/DataMaps.php b/config/DataMaps.php index 532f940..44709fc 100644 --- a/config/DataMaps.php +++ b/config/DataMaps.php @@ -56,7 +56,8 @@ return [ 'displayName' => DataAccess\Varchar('display_name'), 'name' => DataAccess\VO('Name'), 'tags' => DataAccess\VOArray('Tag', 'getTags'), // TODO: Make VarcharArray class - 'facebookId' => DataAccess\Varchar('facebook_id') + 'facebookId' => DataAccess\Varchar('facebook_id'), + 'quota' => DataAccess\Int('quota') ] ], diff --git a/config/Routes.php b/config/Routes.php index c3b28ca..bdcd7ab 100644 --- a/config/Routes.php +++ b/config/Routes.php @@ -40,5 +40,11 @@ return [ 'method' => ['GET'], 'controller' => 'File', 'action' => 'serveBanner' + ], + + '/files/pack/:hash' => [ + 'method' => ['GET'], + 'controller' => 'File', + 'action' => 'servePack' ] ]; -- 2.11.0