Merge changes from home PC
[rock.divinelegy.git] / Services / ZipParser.php
1 <?php
2
3 namespace Services;
4
5 use Exception;
6 use Domain\Exception\InvalidDifficultyException;
7 use Domain\Exception\InvalidDanceModeException;
8 use ZipArchive;
9 use Services\ISimfileParser;
10 use Services\IBannerExtracter;
11 use Domain\Entities\IFile;
12 use Domain\Entities\StepMania\ISimfileStepByStepBuilder;
13 use Domain\Entities\StepMania\IPackStepByStepBuilder;
14 use Services\IZipParser;
15 use Services\IUserSession;
16 use Services\IStatusReporter;
17 use Services\InvalidSmFileException;
18 use Services\IConfigManager;
19
20 class ZipParser implements IZipParser
21 {
22 private $_za;
23 private $_smFiles = array();
24 private $_smParser;
25 private $_smBuilder;
26 private $_packBuilder;
27 private $_bannerExtracter;
28 private $_userSession;
29 private $_file;
30 private $_statusReporter;
31 private $_configManager;
32
33 public function __construct(
34 ISimfileParser $smParser,
35 ISimfileStepByStepBuilder $smBuilder,
36 IPackStepByStepBuilder $packBuilder,
37 IBannerExtracter $bannerExtracter,
38 IUserSession $userSession,
39 IStatusReporter $statusReporter,
40 IConfigManager $configManager
41 ) {
42 $this->_smParser = $smParser;
43 $this->_smBuilder = $smBuilder;
44 $this->_packBuilder = $packBuilder;
45 $this->_bannerExtracter = $bannerExtracter;
46 $this->_userSession = $userSession;
47 $this->_statusReporter = $statusReporter;
48 $this->_configManager = $configManager;
49 }
50
51 public function parse(IFile $file)
52 {
53 $this->_file = $file;
54 $this->_za = new ZipArchive();
55 //XXX: We assume all files are zips. Should be enforced by validation elsewhere.
56 $res = $this->_za->open($this->_configManager->getDirective('filesPath') . '/StepMania/' . $file->getHash() . '.zip');
57
58 if($res !== true) throw new Exception ('Could not open zip for reading.');
59 $this->findSms();
60 }
61
62 public function pack()
63 {
64 if(count($this->_smFiles) > 1)
65 {
66 $packname = $this->packNameFromFiles();
67 $banner = $this->_bannerExtracter->extractPackBanner($this->_configManager->getDirective('filesPath') . '/StepMania/' . $this->_file->getHash() . '.zip', $packname);
68
69 /* @var $builder \Domain\Entities\StepMania\PackStepByStepBuilder */
70 $builder = $this->_packBuilder;
71 return $builder->With_Title($packname)
72 ->With_Uploader($this->_userSession->getCurrentUser())
73 //->With_Simfiles($this->_smFiles)
74 ->With_Simfiles(array())
75 ->With_Banner($banner)
76 ->With_File($this->_file)
77 ->build();
78 }
79 }
80
81 public function simfiles()
82 {
83 return $this->_smFiles;
84 }
85
86 public function isPack()
87 {
88 return count($this->_smFiles) > 1;
89 }
90
91 public function isSingle()
92 {
93 return count($this->_smFiles) == 1;
94 }
95
96 private function findSms()
97 {
98 for($i=0; $i<$this->_za->numFiles; $i++)
99 {
100 $stat = $this->_za->statIndex($i);
101 if(pathinfo($stat['name'], PATHINFO_EXTENSION) == 'sm')
102 {
103 $path = realpath($this->_configManager->getDirective('filesPath') . '/StepMania/' . $this->_file->getHash() . '.zip');
104 $smData = file_get_contents('zip://' . $path . '#' . $stat['name']);
105 $this->_smFiles[$stat['name']] = $smData;
106 }
107 }
108
109 //XXX: Hack. SmDataToSmClass needs to know whether we are dealing with a pack
110 //or single, but to do that we simply check the number of found sm files. To overcome this
111 //first populate the smFiles array with the raw sm data, then apply SmDataToSmClass on each
112 //array element. This way the check is accurate and the array gets populated as expected.
113 foreach($this->_smFiles as $index => $data)
114 {
115 try
116 {
117 $this->_smFiles[$index] = $this->SmDataToSmClass($data, $index);
118 } catch(Exception $e) {
119 //Exceptions we care about at this stage
120 if(!$e instanceof InvalidSmFileException &&
121 !$e instanceof InvalidDanceModeException &&
122 !$e instanceof InvalidDifficultyException)
123 {
124 throw $e;
125 }
126
127 //Add to messages.
128 $this->_statusReporter->addMessage($e->getMessage() . ' in ' . $index);
129 unset($this->_smFiles[$index]);
130 }
131 }
132 //$this->_smFiles = array_map(array($this, 'SmDataToSmClass'), $this->_smFiles);
133 }
134
135 private function packNameFromFiles()
136 {
137 $packName = '';
138 $smpaths = array_keys($this->_smFiles);
139 foreach($smpaths as $path)
140 {
141 $pathComponents = explode('/', $path);
142
143 if(empty($packName)) $packName = $pathComponents[0];
144
145 if($packName != $pathComponents[0])
146 throw new Exception('Malformed zip. I found more than 1 sm file but the directory structure is not consistent with a pack.');
147 }
148
149 return $packName;
150 }
151
152 private function SmDataToSmClass($smData, $index)
153 {
154 $parser = $this->_smParser;
155 $parser->parse($smData);
156
157 $bannerIndex = $this->resolveBannerIndex($parser->banner(), $index);
158 $banner = $this->_bannerExtracter->extractSongBanner(realpath($this->_configManager->getDirective('filesPath') . '/StepMania/' . $this->_file->getHash() . '.zip'), $bannerIndex);
159
160 $file = $this->isPack() ? null : $this->_file;
161
162 return $this->_smBuilder->With_Title($parser->title())
163 ->With_Artist($parser->artist())
164 ->With_Uploader($this->_userSession->getCurrentUser()) //obj
165 ->With_BPM($parser->bpm())
166 ->With_BpmChanges($parser->bpmChanges())
167 ->With_Stops($parser->stops())
168 ->With_FgChanges($parser->fgChanges())
169 ->With_BgChanges($parser->bgChanges())
170 ->With_Steps($parser->steps())
171 ->With_Simfile($file)
172 ->With_Banner($banner)
173 ->build();
174 }
175
176 private function resolveBannerIndex($bannerDirective, $smFileIndex)
177 {
178 if(strpos($bannerDirective, '../') !== false || strpos($bannerDirective, '..\\') !== false)
179 {
180 $pathInfo = pathinfo($smFileIndex);
181 return str_replace('..', dirname($pathInfo['dirname']), $bannerDirective);
182 }
183
184 return dirname($smFileIndex) . '/' . $bannerDirective;
185 }
186 }