\r
namespace DataAccess\DataMapper;\r
\r
+use Exception;\r
use Domain\Entities\IDivineEntity;\r
use DataAccess\IDatabaseFactory;\r
use DataAccess\DataMapper\IDataMapper;\r
\r
public function save(IDivineEntity $entity)\r
{\r
- $queries = AbstractPopulationHelper::generateUpdateSaveQuery($this->_maps, $entity, $entity->getId(), $this->_db);\r
- $mergeMap = array();\r
- $flattened = array();\r
+ try {\r
+ $this->_db->beginTransaction();\r
+ $queries = AbstractPopulationHelper::generateUpdateSaveQuery($this->_maps, $entity, $entity->getId(), $this->_db);\r
+ $mergeMap = array();\r
+ $flattened = array();\r
\r
- foreach($queries as $index => $query)\r
- {\r
- $this_table = $query['table'];\r
- $this_columns = $query['columns'];\r
-\r
- \r
- for($i = $index+1; $i<count($queries); $i++)\r
+ foreach($queries as $index => $query)\r
{\r
- if(\r
- $queries[$i]['table'] == $this_table &&\r
- !array_key_exists($i, $mergeMap) &&\r
- !isset($query['id'])) //only merge create queries, updates are fine to run multiple times\r
+ $this_table = $query['table'];\r
+ $this_columns = $query['columns'];\r
+\r
+\r
+ for($i = $index+1; $i<count($queries); $i++)\r
{\r
- //XXX: This whole biz is tricky. Basically the problem is that when creating a new simfile,\r
- //the datamapper spews out a bunch of create queries. When parsing a simfile for example, there can\r
- //be huge redundency - it may produce 5 queries that all create the same step artist, for example.\r
- //We attempt to flatten equivalent queries. Originally I was basing it purely on the table name or something,\r
- //but that is not enough. In the case of steps, it ends up mergin all the steps together, so we need to\r
- //check if the arrays are equal as well, which is what this does.\r
- if($this_columns === $queries[$i]['columns'])\r
- {\r
- //need to keep track of what we merged as future queries might reference the old ids.\r
- $mergeMap[$i] = $index;\r
- }\r
- \r
- //XXX: Another thing that might happen is we have to create queries running on the same table, but with unique columns.\r
- //In this case, we can take the columns of one and put it into the other. Otherwise we create two records when we really\r
- //should have only one. An example of this is when a user is created, a query to add the country to users_meta is run,\r
- //and then _another_ to add firstname, lastname and user_id. It should really all be done in one query.\r
- \r
- //Make sure both queries are for the same table, and the both relate back to the main query\r
- if($this_table == $queries[$i]['table'] && in_array('%MAIN_QUERY_ID%', $this_columns) && in_array('%MAIN_QUERY_ID%', $queries[$i]['columns']))\r
+ if(\r
+ $queries[$i]['table'] == $this_table &&\r
+ !array_key_exists($i, $mergeMap) &&\r
+ !isset($query['id'])) //only merge create queries, updates are fine to run multiple times\r
{\r
- $this_column_names = array_keys($this_columns);\r
- $other_column_names = array_keys($queries[$i]['columns']);\r
- $combine = true;\r
- foreach($this_column_names as $column_name)\r
+ //XXX: This whole biz is tricky. Basically the problem is that when creating a new simfile,\r
+ //the datamapper spews out a bunch of create queries. When parsing a simfile for example, there can\r
+ //be huge redundency - it may produce 5 queries that all create the same step artist, for example.\r
+ //We attempt to flatten equivalent queries. Originally I was basing it purely on the table name or something,\r
+ //but that is not enough. In the case of steps, it ends up mergin all the steps together, so we need to\r
+ //check if the arrays are equal as well, which is what this does.\r
+ if($this_columns === $queries[$i]['columns'])\r
{\r
- if($this_columns[$column_name] != '%MAIN_QUERY_ID%' && in_array($column_name, $other_column_names))\r
- {\r
- $combine = false;\r
- }\r
+ //need to keep track of what we merged as future queries might reference the old ids.\r
+ $mergeMap[$i] = $index;\r
}\r
- \r
- if($combine)\r
+\r
+ //XXX: Another thing that might happen is we have to create queries running on the same table, but with unique columns.\r
+ //In this case, we can take the columns of one and put it into the other. Otherwise we create two records when we really\r
+ //should have only one. An example of this is when a user is created, a query to add the country to users_meta is run,\r
+ //and then _another_ to add firstname, lastname and user_id. It should really all be done in one query.\r
+\r
+ //Make sure both queries are for the same table, and the both relate back to the main query\r
+ if($this_table == $queries[$i]['table'] && in_array('%MAIN_QUERY_ID%', $this_columns) && in_array('%MAIN_QUERY_ID%', $queries[$i]['columns']))\r
{\r
- $this_columns = array_merge($this_columns, $queries[$i]['columns']);\r
- $mergeMap[$i] = $index;\r
+ $this_column_names = array_keys($this_columns);\r
+ $other_column_names = array_keys($queries[$i]['columns']);\r
+ $combine = true;\r
+ foreach($this_column_names as $column_name)\r
+ {\r
+ if($this_columns[$column_name] != '%MAIN_QUERY_ID%' && in_array($column_name, $other_column_names))\r
+ {\r
+ $combine = false;\r
+ }\r
+ }\r
+\r
+ if($combine)\r
+ {\r
+ $this_columns = array_merge($this_columns, $queries[$i]['columns']);\r
+ $mergeMap[$i] = $index;\r
+ }\r
}\r
}\r
}\r
+\r
+ if(!array_key_exists($index, $mergeMap)) {\r
+ $prepared = isset($query['prepared']) ? $query['prepared'] : null;\r
+ $id = isset($query['id']) ? $query['id'] : null;\r
+\r
+ $flattened[$index] = array(\r
+ 'columns' => $this_columns,\r
+ 'table' => $this_table,\r
+ 'prepared' => $prepared,\r
+ 'id' => $id\r
+ );\r
+ }\r
}\r
- \r
- if(!array_key_exists($index, $mergeMap)) {\r
- $prepared = isset($query['prepared']) ? $query['prepared'] : null;\r
- $id = isset($query['id']) ? $query['id'] : null;\r
-\r
- $flattened[$index] = array(\r
- 'columns' => $this_columns,\r
- 'table' => $this_table,\r
- 'prepared' => $prepared,\r
- 'id' => $id\r
- );\r
- }\r
- }\r
- \r
- $queries = array();\r
- \r
- foreach($flattened as $index => $info)\r
- {\r
- if(isset($info['id']))\r
+\r
+ $queries = array();\r
+\r
+ foreach($flattened as $index => $info)\r
{\r
- $query = $info['prepared'];\r
- $query = substr($query, 0, -2);\r
- $query .= sprintf(' WHERE id=%u', $info['id']);\r
- } else {\r
- $query = sprintf('INSERT INTO %s (%s) VALUES (%s)',\r
- $info['table'],\r
- implode(', ', array_keys($info['columns'])),\r
- implode(', ', $info['columns']));\r
+ if(isset($info['id']))\r
+ {\r
+ $query = $info['prepared'];\r
+ $query = substr($query, 0, -2);\r
+ $query .= sprintf(' WHERE id=%u', $info['id']);\r
+ } else {\r
+ $query = sprintf('INSERT INTO %s (%s) VALUES (%s)',\r
+ $info['table'],\r
+ implode(', ', array_keys($info['columns'])),\r
+ implode(', ', $info['columns']));\r
+ }\r
+\r
+ $queries[$index] = $query;\r
}\r
\r
- $queries[$index] = $query;\r
- }\r
+ // if($queries['TYPE'] == AbstractPopulationHelper::QUERY_TYPE_CREATE)\r
+ // {\r
+ $idMap = [];\r
+ foreach($queries as $index => $query)\r
+ { \r
+ $runQuery = true;\r
+ //originally was preg_quote('%').'(.*?)'.preg_quote('%') but that failed with things like:\r
+ //...VALUES ('Voyager Full 50%', %INDEX_REF_0%\r
+ //it picked up ', \r
+ //so now only find ones with INDEX_REF and double check that MAIN QUERY isn't there.\r
+ if (preg_match_all('/'.preg_quote('%INDEX_REF_').'(.*?)'.preg_quote('%').'/s', $query, $matches)) {\r
+ foreach($matches[1] as $index_ref)\r
+ {\r
+ //if($index_ref != 'MAIN_QUERY_ID')\r
+ //if(strpos($query, '%MAIN_QUERY_ID%') === false)\r
+ //{\r
+ $index_id = str_replace('INDEX_REF_', '', $index_ref);\r
+ $query = str_replace('%INDEX_REF_' . $index_id . '%', $idMap['INDEX_REF_' . $index_id], $query);\r
+ //} else {\r
+ // $runQuery = false;\r
+ //}\r
+ }\r
+ }\r
\r
- // if($queries['TYPE'] == AbstractPopulationHelper::QUERY_TYPE_CREATE)\r
- // {\r
- $idMap = [];\r
- foreach($queries as $index => $query)\r
- { \r
- $runQuery = true;\r
- if (preg_match_all('/'.preg_quote('%').'(.*?)'.preg_quote('%').'/s', $query, $matches)) {\r
- foreach($matches[1] as $index_ref)\r
+ //if we don't need the main query we can run this\r
+ if(strpos($query, '%MAIN_QUERY_ID%') === false)\r
{\r
- if($index_ref != 'MAIN_QUERY_ID')\r
- {\r
- $index_id = str_replace('INDEX_REF_', '', $index_ref);\r
- $query = str_replace('%INDEX_REF_' . $index_id . '%', $idMap['INDEX_REF_' . $index_id], $query);\r
- } else {\r
- $runQuery = false;\r
+ $statement = $this->_db->prepare($query);\r
+ $statement->execute();\r
+ //$refIndex = $index+1; This was being used as the index for idMap below. I have nfi why I was adding 1.\r
+ $idMap['INDEX_REF_' . $index] = $this->_db->lastInsertId();\r
+\r
+ foreach($mergeMap as $oldIndex => $mergedIndex) {\r
+ if($mergedIndex == $index) {\r
+ $idMap['INDEX_REF_' . $oldIndex] = $idMap['INDEX_REF_' . $index];\r
+ }\r
}\r
+\r
+ unset($queries[$index]);\r
+ } else {\r
+ //update query so that other references are resolved.\r
+ $queries[$index] = $query;\r
}\r
}\r
\r
- if($runQuery)\r
+ //at this point we have queries left that depend on the main query id\r
+ foreach($queries as $query)\r
{\r
+ $query = str_replace('%MAIN_QUERY_ID%', end($idMap), $query);\r
$statement = $this->_db->prepare($query);\r
$statement->execute();\r
- //$refIndex = $index+1; This was being used as the index for idMap below. I have nfi why I was adding 1.\r
- $idMap['INDEX_REF_' . $index] = $this->_db->lastInsertId();\r
- \r
- foreach($mergeMap as $oldIndex => $mergedIndex) {\r
- if($mergedIndex == $index) {\r
- $idMap['INDEX_REF_' . $oldIndex] = $idMap['INDEX_REF_' . $index];\r
- }\r
- }\r
- \r
- unset($queries[$index]);\r
- } else {\r
- //update query so that other references are resolved.\r
- $queries[$index] = $query;\r
}\r
- }\r
+ //}\r
+\r
+ if(!$entity->getId()) $entity->setId(end($idMap));\r
\r
- //at this point we have queries left that depend on the main query id\r
- foreach($queries as $query)\r
- {\r
- $query = str_replace('%MAIN_QUERY_ID%', end($idMap), $query);\r
- $statement = $this->_db->prepare($query);\r
- $statement->execute();\r
- }\r
- //}\r
- \r
- if(!$entity->getId()) $entity->setId(end($idMap));\r
- \r
- return $entity;\r
+ $this->_db->commit();\r
+ \r
+ return $entity;\r
+ } catch (Exception $e) {\r
+ $this->_db->rollBack();\r
+ throw $e;\r
+ }\r
}\r
\r
//TODO: Implement\r
namespace Services;
-use Services\ISimfileParser;
use Exception;
+use Services\ISimfileParser;
+use Services\IStatusReporter;
+
+class InvalidSmFileException extends Exception{}
class SimfileParser implements ISimfileParser
{
private $_smFileLines;
+ private $_statusReporter;
+
+ public function __construct(IStatusReporter $statusReporter)
+ {
+ $this->_statusReporter = $statusReporter;
+ }
public function parse($simfileData)
{
public function title()
{
$title = $this->extractKey('TITLE');
- if(!$title) throw new Exception ('Invalid SM file. TITLE missing');
+ if(!$title) throw new InvalidSmFileException('Invalid SM file. TITLE missing');
//XXX: UTF8 encode to deal with unusual character that crop up in weeaboo shit.
return utf8_encode($title);
if($displayBpm)
{
$bpmRange = explode(":",$displayBpm);
- $bpmRange[1] = @$bpmRange[1] ?: $bpmRange[0];
+ $bpmRange[1] = isset($bpmRange[1]) && is_numeric($bpmRange[1]) ?: $bpmRange[0];
}
//XXX: Originally I had an else statement for this. But turns out some SM's have * as the display bpm
public function subtitle()
{
$subtitle = $this->extractKey('SUBTITLE');
- if(!$subtitle) throw new Exception ('Invalid SM file. SUBTITLE missing');
+ if(!$subtitle) return null;
return $subtitle;
}
{
$noteData = trim(substr($line, $pos + 9));
$steps = $this->stepchartFromNoteData($noteData);
-
+
//XXX: Sometimes we get a cabinet lights chart, those return false for getGame.
//We don't want to store cabinet lights, so just ignore it.
if($steps->getMode()->getGame()) $allSteps[] = $steps;
}
}
- if(empty($allSteps)) throw new Exception('Invalid Sm file. NOTES missing');
+ if(empty($allSteps)) throw new InvalidSmFileException('Invalid Sm file. NOTES missing');
return $allSteps;
}
return new \Domain\VOs\StepMania\StepChart(
new \Domain\VOs\StepMania\DanceMode($stepData[0]),
new \Domain\VOs\StepMania\Difficulty($stepData[2]),
- empty($stepData[1]) ? null : new \Domain\VOs\StepMania\StepArtist($stepData[1]),
- $stepData[3]
+ empty($stepData[1]) ? null : new \Domain\VOs\StepMania\StepArtist(utf8_encode($stepData[1])),
+ //XXX: Fuck you whoever made me do this. http://dev.mysql.com/doc/refman/5.5/en/integer-types.html
+ $stepData[3] <= 18446744073709551615 ? $stepData[3] : 9999999999999999999
);
}
private function extractKey($key)
{
+ //Throw regular exception here, this has nothing to do with the SM file.
if(empty($this->_smFileLines)) throw new Exception('SM file data not set.');
foreach ($this->_smFileLines as $line)
foreach($bpms as $bpm)
{
- $bpmMeasure = explode('=', $bpm);
- $bpmValue = floatval($bpmMeasure[1]);
+ $bpmMeasure = explode('=', $bpm);
+ $bpmValue = floatval($bpmMeasure[1]);
- if(empty($bpmRange['low'])) $bpmRange['low'] = $bpmRange['high'] = $bpmValue;
+ if(empty($bpmRange['low'])) $bpmRange['low'] = $bpmRange['high'] = $bpmValue;
+ //XXX: Anything bigger or smaller than this cannot be stored by MySQLs bigint type http://dev.mysql.com/doc/refman/5.5/en/integer-types.html
+ //fuck you if your simfile has bpms this high/low what's wrong with you.
+ if($bpmValue <= 9223372036854775807 && $bpmValue >= -9223372036854775808)
+ {
$bpmRange['high'] = ($bpmValue > $bpmRange['high']) ? $bpmValue : $bpmRange['high'];
$bpmRange['low'] = ($bpmValue < $bpmRange['low']) ? $bpmValue : $bpmRange['low'];
+ }
}
return array($bpmRange['low'], $bpmRange['high']);
namespace Services;
use Exception;
+use Domain\Exception\InvalidDifficultyException;
+use Domain\Exception\InvalidDanceModeException;
use ZipArchive;
use Services\ISimfileParser;
use Services\IBannerExtracter;
use Domain\Entities\StepMania\IPackStepByStepBuilder;
use Services\IZipParser;
use Services\IUserSession;
+use Services\IStatusReporter;
+use Services\InvalidSmFileException;
class ZipParser implements IZipParser
{
private $_bannerExtracter;
private $_userSession;
private $_file;
+ private $_statusReporter;
public function __construct(
ISimfileParser $smParser,
ISimfileStepByStepBuilder $smBuilder,
IPackStepByStepBuilder $packBuilder,
IBannerExtracter $bannerExtracter,
- IUserSession $userSession
+ IUserSession $userSession,
+ IStatusReporter $statusReporter
) {
$this->_smParser = $smParser;
$this->_smBuilder = $smBuilder;
$this->_packBuilder = $packBuilder;
$this->_bannerExtracter = $bannerExtracter;
$this->_userSession = $userSession;
+ $this->_statusReporter = $statusReporter;
}
public function parse(IFile $file)
//or single, but to do that we simply check the number of found sm files. To overcome this
//first populate the smFiles array with the raw sm data, then apply SmDataToSmClass on each
//array element. This way the check is accurate and the array gets populated as expected.
- $this->_smFiles = array_map(array($this, 'SmDataToSmClass'), $this->_smFiles);
+ foreach($this->_smFiles as $index => $data)
+ {
+ try
+ {
+ $this->_smFiles[$index] = $this->SmDataToSmClass($data);
+ } catch(Exception $e) {
+ //Exceptions we care about at this stage
+ if(!$e instanceof InvalidSmFileException &&
+ !$e instanceof InvalidDanceModeException &&
+ !$e instanceof InvalidDifficultyException)
+ {
+ throw $e;
+ }
+
+ //Add to messages.
+ $this->_statusReporter->addMessage($e->getMessage() . ' in ' . $index);
+ unset($this->_smFiles[$index]);
+ }
+ }
+ //$this->_smFiles = array_map(array($this, 'SmDataToSmClass'), $this->_smFiles);
}
private function packNameFromFiles()
$parser->parse($smData);
$banner = $this->_bannerExtracter->extractSongBanner(realpath('../files/StepMania/' . $this->_file->getHash() . '.zip'), $parser->banner());
$file = $this->isPack() ? null : $this->_file;
-
+
return $this->_smBuilder->With_Title($parser->title())
->With_Artist($parser->artist())
->With_Uploader($this->_userSession->getCurrentUser()) //obj