1<?php
2/**
3 * ProjectFile: a class that declares a single poject-file in a project
4 * definition file.
5 *
6 * @author     Junling Ma <junlingm@gmail.com>
7 */
8
9class ProjectFile {
10	protected $name = NULL;
11	protected $deps = NULL;
12
13	public function __construct($file) {
14		$this->name = $file->name();
15		$this->deps = $file->dependency();
16	}
17
18	public function name() { return $this->name; }
19	public function dependency() { return $this->deps; }
20	public function changed_on_disk() { return $this->changed_on_disk; }
21
22	public function path($project_path) {
23		return $project_path . $this->name;
24	}
25
26	public function delete($project_path) {
27		$path = $this->path($project_path);
28		if (file_exists($path)) {
29        	$this->delete_media_file($project_path);
30        	unlink($path);
31        }
32	}
33
34	public function copy($project_path, $file) {
35		$this->deps = $file->dependency();
36	}
37
38	public function type() { return NULL; }
39
40	public function equal($project_path, $file) {
41		return $this->type() == $file->type()
42			&& $this->deps == $file->dependency();
43	}
44
45	public static function create($project, $file) {
46		$class = 'Project' . ucfirst($file->type());
47		return new $class($project, $file);
48	}
49
50	public function time($project_path) {
51		$path = $this->path($project_path);
52		$time = (file_exists($path)) ? filemtime($path) : NULL;
53		return $time;
54	}
55
56	public function content($project_path) {
57		$path = $this->path($project_path);
58		if (file_exists($path))
59			return file_get_contents($path);
60		return NULL;
61	}
62
63	protected function update_media_file($project_path) {
64    	$path = $this->path($project_path);
65    	if (is_link($path)) return;
66        global $ID;
67        $id = getNS($ID) . ':' . $this->name;
68        $media_path = mediaFN($id);
69    	global $media_file_revision_limit;
70    	if ($media_file_revision_limit > 0 &&
71        	filesize($media_path) > $media_file_revision_limit) {
72        	unlink($media_path);
73        	msg("File $id is over the \"media file revision limit\". A revision will not be saved");
74    	}
75    	list($ext, $mime) = mimetype($path);
76    	$data[0] = $path;
77    	$data[1] = mediaFN($id);
78    	$data[2] = $id;
79    	$data[3] = $mime;
80    	$data[4] = file_exists(mediaFN($id));
81    	$data[5] = 'copy_updated_file';
82    	trigger_event('MEDIA_UPLOAD_FINISH', $data, '_media_upload_action', true);
83	}
84
85	protected function delete_media_file($project_path) {
86        global $ID;
87        $id = getNS($ID) . ':' . $this->name;
88    	$media_path = mediaFN($id);
89    	if (file_exists($media_path)) {
90        	media_saveOldRevision($id);
91        	unlink($media_path);
92    	}
93	}
94}
95
96class ProjectSource extends ProjectFile {
97	public function __construct($project, $file) {
98		parent::__construct($file);
99		$this->set_content($project->path(), $file->content());
100	}
101
102	public function is_target() { return false; }
103	public function type() { return SOURCE; }
104
105	public function equal($project_path, $file) {
106		if (!parent::equal($project_path, $file)) return false;
107		return $this->content($project_path) == $file->content();
108	}
109
110	private function set_content($project_path, $content) {
111		file_put_contents($this->path($project_path), $content);
112        $this->update_media_file($project_path);
113	}
114
115	public function copy($project_path, $file) {
116		parent::copy($project_path, $file);
117		$this->set_content($project_path, $file->content());
118	}
119
120	public function makable() { return true; }
121}
122
123class ProjectGenerated extends ProjectFile {
124	protected $recipe = NULL;
125	protected $last_made = NULL;
126
127	public function __construct($project, $file) {
128		parent::__construct($file);
129		$this->recipe = $file->recipe();
130	}
131
132	public function equal($project_path, $file) {
133		return parent::equal($project_path, $file)
134			&& $this->recipe == $file->recipe();
135	}
136
137	public function type() { return TARGET; }
138	public function recipe() { return $this->recipe; }
139
140	public function copy($project_path, $file) {
141		parent::copy($project_path, $file);
142		$this->recipe = $file->recipe();
143	}
144
145	public function delete($project_path, $delete_log = false) {
146		parent::delete($project_path);
147		if ($delete_log) {
148			$path = $this->path($project_path) . '.make.log';
149			if (file_exists($path)) unlink($path);
150		}
151	}
152
153	public function make($working_path) {
154		$current_dir = getcwd();
155		chdir($working_path);
156		$path = $working_path . $this->name;
157		if (!$this->recipe) return 0;
158		if (file_exists($path)) unlink($path);
159		$log = $this->log_file($working_path);
160		if (file_exists($log)) unlink($log);
161		$command = "/bin/sh -e >$log 2>&1";
162		$recipe = $this->recipe();
163		$f = popen($command, 'w');
164		fprintf($f, "%s\n", $this->recipe());
165		$result = pclose($f);
166        if ($result === 0)
167        	$this->update_media_file($working_path);
168        else $this->delete_media_file($working_path);
169		chdir($current_dir);
170		return $result;
171	}
172
173	public function set_last_made_time($working_path) {
174		$this->last_made = $this->time($working_path);
175	}
176
177	public function needs_update($working_path) {
178		if ($this->last_made == NULL) return true;
179		return $this->time($working_path) != $this->last_made;
180	}
181
182	public function makable() {
183		if ($this->recipe === NULL) return false;
184		return (trim($this->recipe) != "");
185	}
186
187	public function is_target() { return true; }
188
189	protected function log_file($working_path) {
190		return $this->path($working_path) . '.make.log';
191	}
192
193	protected function log($working_path, $content) {
194		$log = $this->log_file($working_path);
195		file_put_contents($log, $content);
196	}
197}
198
199class ProjectCrossLink extends ProjectGenerated {
200	protected $crosslink = NULL;
201
202	public function __construct($project, $file) {
203		parent::__construct($project, $file);
204		$this->crosslink = $file->crosslink();
205	}
206
207	public function crosslink() { return $this->crosslink; }
208
209	public function type() { return CROSSLINK; }
210
211	public function equal($project_path, $file) {
212		return parent::equal($project_path, $file)
213			&& $this->crosslink == $file->crosslink();
214	}
215
216	public function copy($project_path, $file) {
217		parent::copy($project_path, $file);
218		$this->crosslink = $file->crosslink();
219	}
220
221	protected function crosslink_path() {
222		if (!$this->crosslink) return NULL;
223		$path = explode(":", $this->crosslink);
224        $media = ($path[0] == '[media]');
225        if ($media) array_shift($path);
226		if (count($path) > 1 && $path[0] != PROJECTS_NAMESPACE) return NULL;
227		$id = implode(':', $path);
228        $project = Project::project();
229        if (!getNS($id))
230            $id = $project->id($id);
231        if ($media) return mediaFN($id);
232        return $project->path($id);
233	}
234
235	public function time($working_path) {
236		$path = $working_path . $this->name;
237		if (!file_exists($path)) return NULL;
238		$lspath = lstat($path);
239		return $lspath['mtime'];
240	}
241
242	public function make($working_path) {
243    	$log = $this->log_file($working_path);
244		if (!$this->crosslink) {
245			file_put_contents($log, "The crosslink is not defined in this file!\n");
246			return 1;
247		}
248		$link = realpath($this->crosslink_path());
249		$path = $this->path($working_path);
250		$crosslink = $this->crosslink;
251		if (!file_exists($link)) {
252			if (file_exists($path)) unlink($path);
253			file_put_contents($log, "file $crosslink does not exist!\n");
254			return 1;
255		}
256		file_put_contents($log, "linked $crosslink");
257		if (file_exists($path))	unlink($path);
258		symlink($link, $path);
259		return 0;
260	}
261
262	public function makable() { return true; }
263}
264
265?>