Quota tracking now works. Quota currently defaults to 100 for each user and resets...
authorCameron Ball <cameron@getapproved.com.au>
Wed, 3 Dec 2014 06:32:18 +0000 (14:32 +0800)
committerCameron Ball <cameron@getapproved.com.au>
Wed, 3 Dec 2014 06:32:18 +0000 (14:32 +0800)
23 files changed:
Controllers/DownloadTestController.php
Controllers/FileController.php
Controllers/SimfileController.php
Controllers/UserAuthController.php
Controllers/UserController.php
DataAccess/DataMapper/DataMapper.php
DataAccess/DataMapper/Helpers/AbstractPopulationHelper.php
Domain/Entities/IUser.php
Domain/Entities/IUserBuilder.php
Domain/Entities/User.php
Domain/Entities/UserBuilder.php
Domain/Entities/UserFactory.php
Domain/Entities/UserStepByStepBuilder.php
Domain/Util.php
Services/Http/HttpResponse.php
Services/Http/IHttpResponse.php
Services/IUserQuota.php [new file with mode: 0644]
Services/IUserSession.php
Services/UserQuota.php [new file with mode: 0644]
Services/UserSession.php
config/DI.php
config/DataMaps.php
config/Routes.php

index c12df99..1f92ab4 100644 (file)
@@ -3,30 +3,20 @@
 namespace Controllers;\r
 \r
 use Controllers\IDivineController;\r
-use DataAccess\IDownloadRepository;\r
-use DataAccess\Queries\DownloadQueryConstraints;\r
-use DateTime;\r
+use Services\IUserQuota;\r
 \r
 class DownloadTestController implements IDivineController\r
 {\r
-    private $_downloadRepository;\r
+    private $_quotaManager;\r
     \r
     public function __construct(\r
-        IDownloadRepository $repository\r
+        IUserQuota $quotaManager\r
     ) {\r
-        $this->_downloadRepository = $repository;\r
+        $this->_quotaManager = $quotaManager;\r
     }\r
     \r
     public function indexAction() {\r
-        $start = new DateTime('0:00 today');\r
-        $end = new DateTime();\r
-        \r
-        $constraints = new DownloadQueryConstraints();\r
-        $constraints->inDateRange($start, $end);\r
-        $downloads = $this->_downloadRepository->findByUserId(4, $constraints);\r
-\r
-        echo '<pre>';\r
-        print_r($downloads);\r
-        echo '</pre>';\r
+        $quota = (($this->_quotaManager->getCurrentUserQuotaRemaining())/1000)/1000;\r
+        echo $quota;\r
     }\r
 }\r
index 4b627db..1fb1cb2 100644 (file)
@@ -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();
+    }
 }
index bff9689..2763fd6 100644 (file)
@@ -13,7 +13,6 @@ use DataAccess\StepMania\IPackRepository;
 use DataAccess\IFileRepository;\r
 use Domain\Entities\StepMania\ISimfile;\r
 use Domain\Entities\IFile;\r
-use Domain\VOs\IFileMirror;\r
 \r
 class SimfileController implements IDivineController\r
 {\r
@@ -22,7 +21,6 @@ class SimfileController implements IDivineController
     private $_fileRepository;\r
     private $_response;\r
     private $_uploadManager;\r
-    private $_userSession;\r
     private $_zipParser;\r
     private $_smoMatcher;\r
     \r
@@ -41,7 +39,6 @@ class SimfileController implements IDivineController
         $this->_simfileRepository = $simfileRepository;\r
         $this->_packRepository = $packRepository;\r
         $this->_fileRepository = $fileRepository;\r
-        $this->_userSession = $userSession;\r
         $this->_zipParser = $zipParser;\r
         $this->_smoMatcher = $smoMatcher;\r
     }\r
@@ -73,6 +70,12 @@ class SimfileController implements IDivineController
             }\r
 \r
             $packMirrors = array();\r
+            \r
+            if($pack->getFile())\r
+            {\r
+                $packMirrors[] = array('source' => 'DivinElegy', 'uri' => 'files/pack/' . $pack->getFile()->getHash());\r
+            }\r
+            \r
             if($pack->getFile()->getMirrors())\r
             {\r
                 foreach($pack->getFile()->getMirrors() as $mirror)\r
index c85988a..0f044d1 100644 (file)
@@ -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);
index 822dd13..62d150a 100644 (file)
@@ -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')
index fd9c242..d5c3d9d 100644 (file)
@@ -23,7 +23,6 @@ class DataMapper implements IDataMapper
     public function map($entityName, IQueryBuilder $queryBuilder)\r
     {\r
         $queryString = $queryBuilder->buildQuery();\r
-\r
         $statement = $this->_db->prepare(sprintf($queryString,\r
             $this->_maps[$entityName]['table']\r
         ));\r
index d32f464..eab5154 100644 (file)
@@ -22,6 +22,9 @@ class AbstractPopulationHelper
             switch(get_class($mapsHelper))\r
             {\r
                 case 'DataAccess\DataMapper\Helpers\IntMapsHelper':\r
+                    if(!empty($row[$mapsHelper->getColumnName()]) && (string)(int)$row[$mapsHelper->getColumnName()] != $row[$mapsHelper->getColumnName()]) throw new Exception('Expected numeric value.');\r
+                    $constructors[$constructor] = (int)$row[$mapsHelper->getColumnName()];\r
+                    break;\r
                 case 'DataAccess\DataMapper\Helpers\VarcharMapsHelper':\r
                     $constructors[$constructor] = $row[$mapsHelper->getColumnName()];\r
                     break;\r
index aefd961..01314bf 100644 (file)
@@ -13,4 +13,5 @@ interface IUser
     public function getYearsStepArtist();\r
     public function getFacebookId();\r
     public function setFacebookId($id);\r
+    public function getQuota();\r
 }\r
index a938f0e..bb5ca03 100644 (file)
@@ -13,5 +13,6 @@ interface IUserBuilder
     public function With_Tags(array $tags);\r
     public function With_FacebookId($id);\r
     public function With_YearsStepArtist($years);\r
+    public function With_Quota($quota);\r
     public function build();\r
 }\r
index dfa67e9..627d682 100644 (file)
@@ -15,19 +15,22 @@ class User extends AbstractEntity implements IUser
     private $_tags;\r
     private $_yearsStepArtist;\r
     private $_facebookId;\r
+    private $_quota;\r
     \r
     public function __construct(\r
         ICountry $country,\r
         $displayName,\r
         IName $name,\r
         array $tags,\r
-        $facebookId\r
+        $facebookId,\r
+        $quota //TODO: Maybe quota should be implemented as an object?\r
     ) {\r
         $this->_country = $country;\r
         $this->_displayName = $displayName;\r
         $this->_name = $name;\r
         $this->_tags = $tags;\r
         $this->_facebookId = $facebookId;\r
+        $this->_quota = $quota;\r
     }\r
         \r
     public function getCountry() {\r
@@ -56,7 +59,13 @@ class User extends AbstractEntity implements IUser
         $this->_facebookId = $id;\r
     }\r
     \r
-    public function getYearsStepArtist() {\r
+    public function getYearsStepArtist()\r
+    {\r
         return $this->_yearsStepArtist;\r
     }\r
+    \r
+    public function getQuota()\r
+    {\r
+        return $this->_quota;\r
+    }\r
 }\r
index 8957a3d..73eeb80 100644 (file)
@@ -16,6 +16,7 @@ class UserBuilder implements IUserBuilder
     private $_tags;\r
     private $_facebookId;\r
     private $_yearsStepArtist;\r
+    private $_quota;\r
     \r
     public function __construct(IUserFactory $userFactory)\r
     {\r
@@ -52,12 +53,18 @@ class UserBuilder implements IUserBuilder
         return $this;\r
     }\r
     \r
+    public function With_Quota($quota)\r
+    {\r
+        $this->_quota = $quota;\r
+    }\r
+    \r
     public function build() {\r
         return $this->_userFactory\r
                     ->createInstance($this->_country,\r
                                      $this->_displayName,\r
                                      $this->_name,\r
                                      $this->_tags,\r
-                                     $this->_facebookId);\r
+                                     $this->_facebookId,\r
+                                     $this->_quota);\r
     }\r
 }
\ No newline at end of file
index 94235be..f2e650d 100644 (file)
@@ -13,7 +13,8 @@ interface IUserFactory
         $displayName,\r
         IName $name,\r
         array $tags,\r
-        $facebookId\r
+        $facebookId,\r
+        $quota\r
     );\r
 }\r
 \r
@@ -24,14 +25,16 @@ class UserFactory implements IUserFactory
         $displayName,\r
         IName $name,\r
         array $tags,\r
-        $facebookId\r
+        $facebookId,\r
+        $quota\r
     ) {\r
         return new User(\r
             $country,\r
             $displayName,\r
             $name,\r
             $tags,\r
-            $facebookId\r
+            $facebookId,\r
+            $quota\r
         );\r
     }\r
 }
\ No newline at end of file
index e586e64..6f8d55c 100644 (file)
@@ -32,6 +32,11 @@ interface IUserStepByStepBuilder_With_Tags
 \r
 interface IUserStepByStepBuilder_With_FacebookId\r
 {\r
+    public function With_Quota($quota);\r
+}\r
+\r
+interface IUserStepByStepBuilder_With_Quota\r
+{\r
     public function With_YearsStepArtist($years); //not going to make this mandatory as it is kind of a joke\r
     public function build();\r
 }\r
@@ -88,6 +93,15 @@ class UserStepByStepBuilder_With_Tags extends AbstractUserStepByStepBuilder impl
 \r
 class UserStepByStepBuilder_With_FacebookId extends AbstractUserStepByStepBuilder implements IUserStepByStepBuilder_With_FacebookId\r
 {\r
+    public function With_Quota($quota)\r
+    {\r
+        $this->_userBuilder->With_Quota($quota);\r
+        return new UserStepByStepBuilder_With_Quota($this->_userBuilder);\r
+    }\r
+}\r
+\r
+class UserStepByStepBuilder_With_Quota extends AbstractUserStepByStepBuilder implements IUserStepByStepBuilder_With_Quota\r
+{\r
     public function With_YearsStepArtist($years) {\r
         $this->_userBuilder->With_YearsStepArtist($years);\r
         return $this;\r
index 1b0d555..df81dbb 100644 (file)
@@ -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
index 3041556..bc6fd3a 100644 (file)
@@ -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);
+    }
 }
index 659cb7e..4797b09 100644 (file)
@@ -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 (file)
index 0000000..0b29062
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+namespace Services;
+
+interface IUserQuota
+{
+    public function getCurrentUserQuotaRemaining();
+}
\ No newline at end of file
index 084aa0b..96b13dd 100644 (file)
@@ -5,6 +5,5 @@ namespace Services;
 interface IUserSession
 {
     public function getCurrentUser();
-    public function getCurrentUserQuota();
 }
 
diff --git a/Services/UserQuota.php b/Services/UserQuota.php
new file mode 100644 (file)
index 0000000..48b545f
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+namespace Services;
+
+use Services\IUserQuota;
+use Services\IUserSession;
+use DataAccess\IDownloadRepository;
+use DataAccess\Queries\DownloadQueryConstraints;
+use DateTime;
+
+class UserQuota implements IUserQuota
+{
+    private $_userSession;
+    private $_downloadRepository;
+    
+    public function __construct(
+        IUserSession $userSession,
+        IDownloadRepository $downloadRepository
+    ) {
+        $this->_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;
+    }
+}
index 53052d6..04505a0 100644 (file)
@@ -24,11 +24,7 @@ class UserSession implements IUserSession
     {
         return $this->_currentUser;
     }
-    
-    public function getCurrentUserQuota() {
-        ;
-    }
-    
+
     private function findToken()
     {        
         if($this->_request->isPost())
index 34e6be0..5d0838b 100644 (file)
@@ -23,6 +23,8 @@ return [
     'Domain\Entities\IFileFactory'                        => DI\object('Domain\Entities\FileFactory'),\r
     'Domain\Entities\IFileBuilder'                        => DI\object('Domain\Entities\FileBuilder'),\r
     'Domain\Entities\IFileStepByStepBuilder'              => DI\object('Domain\Entities\FileStepByStepBuilder'),\r
+    \r
+    'Domain\Entities\IDownloadFactory'                    => DI\object('Domain\Entities\DownloadFactory'),\r
 \r
     //services\r
     'Services\Http\IHttpResponse'                         => DI\object('Services\Http\HttpResponse'),\r
@@ -31,6 +33,7 @@ return [
                                                                 ->constructor(DI\link('router.maps')),\r
     'Services\Uploads\IUploadManager'                     => DI\object('Services\Uploads\UploadManager'),\r
     'Services\IUserSession'                               => DI\object('Services\UserSession'),\r
+    'Services\IUserQuota'                                 => DI\object('Services\UserQuota'),\r
     'Services\Uploads\IFileFactory'                       => DI\object('Services\Uploads\FileFactory'),\r
     'Services\IFacebookSessionFactory'                    => DI\object('Services\FacebookSessionFactory')\r
                                                                 ->constructor(DI\link('facebook.app')),\r
index 532f940..44709fc 100644 (file)
@@ -56,7 +56,8 @@ return [
             'displayName' => DataAccess\Varchar('display_name'),\r
             'name' => DataAccess\VO('Name'),\r
             'tags' => DataAccess\VOArray('Tag', 'getTags'), // TODO: Make VarcharArray class\r
-            'facebookId' => DataAccess\Varchar('facebook_id')\r
+            'facebookId' => DataAccess\Varchar('facebook_id'),\r
+            'quota' => DataAccess\Int('quota')\r
         ]\r
     ],\r
     \r
index c3b28ca..bdcd7ab 100644 (file)
@@ -40,5 +40,11 @@ return [
         'method' => ['GET'],\r
         'controller' => 'File',\r
         'action' => 'serveBanner'\r
+    ],\r
+    \r
+    '/files/pack/:hash' => [\r
+        'method' => ['GET'],\r
+        'controller' => 'File',\r
+        'action' => 'servePack'\r
     ]\r
 ];\r