aboutsummaryrefslogtreecommitdiffstats
path: root/documentation/articles/ComponentAddonProjectSetupHOWTO.asciidoc
blob: e8d063e27c76e6a695ab17b4a1a49048e1dd876f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
---
title: Component Addon Project Setup HOWTO
order: 77
layout: page
---

[[component-add-on-project-setup-howto]]
Component add-on project setup how-to
------------------------------------

This how-to walks you through a complete setup for a project for
developing, building and publishing your own Vaadin UI component
add-ons. The goal here is not to teach how to write an add-on, but to
make the process of setting up your project environment as smooth as
possible. I hope this encourages you to try building and publishing your
own add-ons :)

[[goals-for-the-project-environment]]
Goals for the project environment
---------------------------------

* Fully automated build with Maven
* Allow anyone to re-build your project easily regardless of the IDE:s
* Almost instant save-build-deploy-try cycle
* Simple, but complete, project setup
* Project publishing to GitHub
* Easy publishing of the results to Vaadin Directory

[[install-toolchain]]
Install toolchain
-----------------

If you do not already have the following tools in use, install them:

* Eclipse IDE for Java EE developers from http://www.eclipse.org (Indigo
Service Release 1 was used in this how-to)
* Google Chrome browser from https://www.google.com/chrome/ (other
browsers will do, but Chrome is recommended)
* Eclipse plugins: m4e-wtp, vaadin, egit (optional) and jrebel
(optional) from Marketplace (just select Help->Marketplace... from the
menu)

[[create-a-new-widget-project]]
Create a new widget project
---------------------------

Start project creation wizard: File -> New -> Other... -> "Maven
Project"

Give a proper name for your project and save it under workspace. For
this example I am building a list widget and name it MyList.

Ensure that your Maven archetype catalogs contain
http://repo1.maven.org/maven2/archetype-catalog.xml as remote catalog
and select it.

Select vaadin-archetype-widget from the list.

Give a proper name for the project. I use "org.vaadin" as group id as it
can be used by anyone who wants to contribute non-commercial widgets to
Vaadin project and name of the widget as artifact id in this case i use
"mylist" as example. For a package name use "org.vaadin.mylist".

Observe that pom.xml shows two errors. This is because m2e does not
directly support gwt and vaadin -plugins. To fix the problem, choose the
problems one by one and choose "ignore" quick fix. Then edit the pom.xml
by changing all `<ignore></ignore>` tags to `<execute></execute>` to get the
plugins to execute. Finally, clear the remaining "project configuration
needs update" error with quickfix (that not surprisingly updates project
configuration). In the end, pom.xml should look like
https://raw.github.com/jojule/MyList/56ac906f9cc6442e0817eb0cc945eee023ff9001/pom.xml[this].

Refactor the name of the component you are building.

* Instead of using `MyComponent` and `VMyComponent`, use your own name. In
this example I use `MyList` and `VMyList`.
* Also change the theme directory name from
`src/main/java/org/vaadin/mylist/gwt/public/mywidget` to
`src/main/java/org/vaadin/mylist/gwt/public/mylist`
* and update the reference in `MyWidgetSet.gwt.xml`.
* Also rename `MyWidgetSet.gwt.xml` to `MyListWidgetSet.gwt.xml`
* and update references in `pom.xml` and `web.xml`.

Test that the project compiles and runs by running (Run -> Run as ... ->
Maven Build...) maven goal "package jetty:run". If everything compiles
fine and Jetty server starts, you can access the application at
http://localhost:8080/mylist/. You should see "It works!" on the web
page. Do not worry that the build takes a lot of time, we'll get back to
it in a minute.

Finally, if you prefer to use Git, create a repository for the project.
You could simply choose "Share Project..." from the Project's Team menu.
Choose "Use or create repository in parent folder" and click "Create
Repository". Then, add project resources to commit. Choose pom.xml and
src directory from Navigator view and select Team -> Add to Index. Then
add the rest of the files (.settings, .project, .classpath and target)
to .gitignore with Team -> Ignore. Finally, just do Team -> Commit.

At this point - or later whenever you are ready for it - you can
publish the project to GitHub. Just go to github.com and create a new
repository. Use MyList as the name for the repository. Then follow the
instructions on the screen. In my case, I executed the following command
line commands: `cd /Users/phoenix/Documents/workspace/mylist; git remote
add origin git@github.com:jojule/MyList.git; git push -u origin master`.
You can see the results
https://github.com/jojule/MyList/tree/56ac906f9cc6442e0817eb0cc945eee023ff9001[at
GitHub].

[[save---build---deploy---try]]
Save - Build - Deploy - Try
---------------------------

If it takes minutes each time from code change to seeing that change on
the screen, you are not going to get your component ready anytime soon.
To solve the issue, we use two tools: 1) Google GWT Developer Mode and
2) JRebel. The first one is more important here as the GWT compilation
step is the really slow step, but JRebel also helps as it gives you
instant redeploy for the server-side changes.

To enable JRebel, open project popup menu and choose JRebel -> Generate
rebel.xml in `src/main/java`. Then click "Enable JRebel" on the JRebel tab
for Maven run configuration for "jetty:run". Now when you make any
changes to server-side code - for example to `WidgetTestApplication.java`
- hit save and reload the browser pointing to
http://localhost:8080/mylist/?restartApplication, the changes are
applied immediately. Even better - you can start the project with Debug
As and add break points to the application.

Client-side changes are more tricky as they are compiled from Java to
JavaScript by GWT. To make those changes immediately you, must be
running a GWT Development Mode. This is done by running Maven goal gwt:run
instead of just pointing your web browser to the running application.
Note that must be running both jetty:run and gwt:run concurrently.
gwt:run starts application called "GWT Development Mode". From there you
can launch your browser - or cut-n-paste URL to Chrome - if that is not
your default browser. When the application is started, add
`&restartApplication` parameter to the end of the URL to ensure that the
server-side of the application is reloaded each time you reload the
page. In this case, the full url is
http://127.0.0.1:8080/mylist/?gwt.codesvr=127.0.0.1:9997&restartApplication.
Try making a change to the client-side code (for example `VMyList.java`),
hitting save and reloading the page to see how everything works
together. You can also run gwt:run in Debug As to debug the client-side
code.

Now the "save - build - deploy - try" cycle has been reduced to almost
instant for both client-side as well as server-side changes. Let the
real development begin.

[[developing-a-new-component-for-vaadin]]
Developing a new component for Vaadin
-------------------------------------

Wait for an amazing idea, code like crazy, enjoy and POOOF, there it is
- your own brand new component.

If you need guidance with this,
https://vaadin.com/book/-/page/gwt.html[Book of Vaadin] is a recommended
reading :)

For this example, I implemented a trivial list component. Take a look of
1.0.0 version
https://github.com/jojule/MyList/tree/496a8bdf629154a4da7b83c4a11979272959aa96[at
GitHub], but do not expect too much :) To try it out just do: `git clone
git@github.com:jojule/MyList.git; mvn package; mvn jetty:run` and point
your web browser to http://localhost:8080/mylist/.

[[packaging-and-submitting-the-widget-to-directory]]
Packaging and submitting the widget to directory
------------------------------------------------

Set the version number in pom.xml

Run Maven target "package" and you'll have a ready made package at
target/mylist-1.0.0.jar ready for upload to vaadin directory.

Go to https://vaadin.com/directory/my-components, select UI Component and
click upload.

Fill the form, preview and publish.
kport/48008/stable30'>backport/48008/stable30 Nextcloud server, a safe home for all your data: https://github.com/nextcloud/serverwww-data
aboutsummaryrefslogtreecommitdiffstats
path: root/lib/files.php
blob: 63dd96b9509d91f1846e377c029d7bff1450fc12 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
<?php

/**
* ownCloud
*
* @author Frank Karlitschek
* @copyright 2012 Frank Karlitschek frank@owncloud.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*
*/

/**
 * Class for fileserver access
 *
 */
class OC_Files {
	static $tmpFiles=array();

	/**
	* get the content of a directory
	* @param dir $directory path under datadirectory
	*/
	public static function getDirectoryContent($directory, $mimetype_filter = '') {
		$directory=OC_Filesystem::normalizePath($directory);
		if($directory=='/') {
			$directory='';
		}
		$files = array();
		if (($directory == '/Shared' || substr($directory, 0, 8) == '/Shared/') && OC_App::isEnabled('files_sharing')) {
			if ($directory == '/Shared') {
				$files = OCP\Share::getItemsSharedWith('file', OC_Share_Backend_File::FORMAT_FILE_APP, array('folder' => $directory, 'mimetype_filter' => $mimetype_filter));
			} else {
				$pos = strpos($directory, '/', 8);
				// Get shared folder name
				if ($pos !== false) {
					$itemTarget = substr($directory, 7, $pos - 7);
				} else {
					$itemTarget = substr($directory, 7);
				}
				$files = OCP\Share::getItemSharedWith('folder', $itemTarget, OC_Share_Backend_File::FORMAT_FILE_APP, array('folder' => $directory, 'mimetype_filter' => $mimetype_filter));
			}
		} else {
			$files = OC_FileCache::getFolderContent($directory, false, $mimetype_filter);
			foreach ($files as &$file) {
				$file['directory'] = $directory;
				$file['type'] = ($file['mimetype'] == 'httpd/unix-directory') ? 'dir' : 'file';
				$permissions = OCP\Share::PERMISSION_READ;
				// NOTE: Remove check when new encryption is merged
				if (!$file['encrypted']) {
					$permissions |= OCP\Share::PERMISSION_SHARE;
				}
				if ($file['type'] == 'dir' && $file['writable']) {
					$permissions |= OCP\Share::PERMISSION_CREATE;
				}
				if ($file['writable']) {
					$permissions |= OCP\Share::PERMISSION_UPDATE | OCP\Share::PERMISSION_DELETE;
				}
				$file['permissions'] = $permissions;
			}
			if ($directory == '' && OC_App::isEnabled('files_sharing')) {
				// Add 'Shared' folder
				$files = array_merge($files, OCP\Share::getItemsSharedWith('file', OC_Share_Backend_File::FORMAT_FILE_APP_ROOT));
			}
		}
		usort($files, "fileCmp");//TODO: remove this once ajax is merged
		return $files;
	}



	/**
	* return the content of a file or return a zip file containning multiply files
	*
	* @param dir  $dir
	* @param file $file ; seperated list of files to download
	* @param boolean $only_header ; boolean to only send header of the request
	*/
	public static function get($dir,$files, $only_header = false) {
		if(strpos($files,';')) {
			$files=explode(';',$files);
		}

		if(is_array($files)) {
			self::validateZipDownload($dir,$files);
			$executionTime = intval(ini_get('max_execution_time'));
			set_time_limit(0);
			$zip = new ZipArchive();
			$filename = OC_Helper::tmpFile('.zip');
			if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) {
				exit("cannot open <$filename>\n");
			}
			foreach($files as $file) {
				$file=$dir.'/'.$file;
				if(OC_Filesystem::is_file($file)) {
					$tmpFile=OC_Filesystem::toTmpFile($file);
					self::$tmpFiles[]=$tmpFile;
					$zip->addFile($tmpFile,basename($file));
				}elseif(OC_Filesystem::is_dir($file)) {
					self::zipAddDir($file,$zip);
				}
			}
			$zip->close();
			set_time_limit($executionTime);
		}elseif(OC_Filesystem::is_dir($dir.'/'.$files)) {
			self::validateZipDownload($dir,$files);
			$executionTime = intval(ini_get('max_execution_time'));
			set_time_limit(0);
			$zip = new ZipArchive();
			$filename = OC_Helper::tmpFile('.zip');
			if ($zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)!==TRUE) {
				exit("cannot open <$filename>\n");
			}
			$file=$dir.'/'.$files;
			self::zipAddDir($file,$zip);
			$zip->close();
			set_time_limit($executionTime);
		}else{
			$zip=false;
			$filename=$dir.'/'.$files;
		}
		@ob_end_clean();
		if($zip or OC_Filesystem::is_readable($filename)) {
			header('Content-Disposition: attachment; filename="'.basename($filename).'"');
			header('Content-Transfer-Encoding: binary');
			OC_Response::disableCaching();
			if($zip) {
				ini_set('zlib.output_compression', 'off');
				header('Content-Type: application/zip');
				header('Content-Length: ' . filesize($filename));
			}else{
				header('Content-Type: '.OC_Filesystem::getMimeType($filename));
			}
		}elseif($zip or !OC_Filesystem::file_exists($filename)) {
			header("HTTP/1.0 404 Not Found");
			$tmpl = new OC_Template( '', '404', 'guest' );
			$tmpl->assign('file',$filename);
			$tmpl->printPage();
		}else{
			header("HTTP/1.0 403 Forbidden");
			die('403 Forbidden');
		}
		if($only_header) {
			if(!$zip)
				header("Content-Length: ".OC_Filesystem::filesize($filename));
			return ;
		}
		if($zip) {
			$handle=fopen($filename,'r');
			if ($handle) {
				$chunkSize = 8*1024;// 1 MB chunks
				while (!feof($handle)) {
					echo fread($handle, $chunkSize);
					flush();
				}
			}
			unlink($filename);
		}else{
			OC_Filesystem::readfile($filename);
		}
		foreach(self::$tmpFiles as $tmpFile) {
			if(file_exists($tmpFile) and is_file($tmpFile)) {
				unlink($tmpFile);
			}
		}
	}

	public static function zipAddDir($dir,$zip,$internalDir='') {
		$dirname=basename($dir);
		$zip->addEmptyDir($internalDir.$dirname);
		$internalDir.=$dirname.='/';
		$files=OC_Files::getdirectorycontent($dir);
		foreach($files as $file) {
			$filename=$file['name'];
			$file=$dir.'/'.$filename;
			if(OC_Filesystem::is_file($file)) {
				$tmpFile=OC_Filesystem::toTmpFile($file);
				OC_Files::$tmpFiles[]=$tmpFile;
				$zip->addFile($tmpFile,$internalDir.$filename);
			}elseif(OC_Filesystem::is_dir($file)) {
				self::zipAddDir($file,$zip,$internalDir);
			}
		}
	}
	/**
	* move a file or folder
	*
	* @param dir  $sourceDir
	* @param file $source
	* @param dir  $targetDir
	* @param file $target
	*/
	public static function move($sourceDir,$source,$targetDir,$target) {
		if(OC_User::isLoggedIn() && ($sourceDir != '' || $source != 'Shared')) {
			$targetFile=self::normalizePath($targetDir.'/'.$target);
			$sourceFile=self::normalizePath($sourceDir.'/'.$source);
			return OC_Filesystem::rename($sourceFile,$targetFile);
		} else {
			return false;
		}
	}

	/**
	* copy a file or folder
	*
	* @param dir  $sourceDir
	* @param file $source
	* @param dir  $targetDir
	* @param file $target
	*/
	public static function copy($sourceDir,$source,$targetDir,$target) {
		if(OC_User::isLoggedIn()) {
			$targetFile=$targetDir.'/'.$target;
			$sourceFile=$sourceDir.'/'.$source;
			return OC_Filesystem::copy($sourceFile,$targetFile);
		}
	}

	/**
	* create a new file or folder
	*
	* @param dir  $dir
	* @param file $name
	* @param type $type
	*/
	public static function newFile($dir,$name,$type) {
		if(OC_User::isLoggedIn()) {
			$file=$dir.'/'.$name;
			if($type=='dir') {
				return OC_Filesystem::mkdir($file);
			}elseif($type=='file') {
				$fileHandle=OC_Filesystem::fopen($file, 'w');
				if($fileHandle) {
					fclose($fileHandle);
					return true;
				}else{
					return false;
				}
			}
		}
	}

	/**
	* deletes a file or folder
	*
	* @param dir  $dir
	* @param file $name
	*/
	public static function delete($dir,$file) {
		if(OC_User::isLoggedIn() && ($dir!= '' || $file != 'Shared')) {
			$file=$dir.'/'.$file;
			return OC_Filesystem::unlink($file);
		}
	}

	/**
	* checks if the selected files are within the size constraint. If not, outputs an error page.
	*
	* @param dir   $dir
	* @param files $files
	*/
	static function validateZipDownload($dir, $files) {
		if(!OC_Config::getValue('allowZipDownload', true)) {
			$l = OC_L10N::get('lib');
			header("HTTP/1.0 409 Conflict");
			$tmpl = new OC_Template( '', 'error', 'user' );
			$errors = array(
				array(
					'error' => $l->t('ZIP download is turned off.'),
					'hint' => $l->t('Files need to be downloaded one by one.') . '<br/><a href="javascript:history.back()">' . $l->t('Back to Files') . '</a>',
				)
			);
			$tmpl->assign('errors', $errors);
			$tmpl->printPage();
			exit;
		}

		$zipLimit = OC_Config::getValue('maxZipInputSize', OC_Helper::computerFileSize('800 MB'));
		if($zipLimit > 0) {
			$totalsize = 0;
			if(is_array($files)) {
				foreach($files as $file) {
					$totalsize += OC_Filesystem::filesize($dir.'/'.$file);
				}
			}else{
				$totalsize += OC_Filesystem::filesize($dir.'/'.$files);
			}
			if($totalsize > $zipLimit) {
				$l = OC_L10N::get('lib');
				header("HTTP/1.0 409 Conflict");
				$tmpl = new OC_Template( '', 'error', 'user' );
				$errors = array(
					array(
						'error' => $l->t('Selected files too large to generate zip file.'),
						'hint' => 'Download the files in smaller chunks, seperately or kindly ask your administrator.<br/><a href="javascript:history.back()">' . $l->t('Back to Files') . '</a>',
					)
				);
				$tmpl->assign('errors', $errors);
				$tmpl->printPage();
				exit;
			}
		}
	}

	/**
	* try to detect the mime type of a file
	*
	* @param  string  path
	* @return string  guessed mime type
	*/
	static function getMimeType($path) {
		return OC_Filesystem::getMimeType($path);
	}

	/**
	* get a file tree
	*
	* @param  string  path
	* @return array
	*/
	static function getTree($path) {
		return OC_Filesystem::getTree($path);
	}

	/**
	* pull a file from a remote server
	* @param  string  source
	* @param  string  token
	* @param  string  dir
	* @param  string  file
	* @return string  guessed mime type
	*/
	static function pull($source,$token,$dir,$file) {
		$tmpfile=tempnam(get_temp_dir(),'remoteCloudFile');
		$fp=fopen($tmpfile,'w+');
		$url=$source.="/files/pull.php?token=$token";
		$ch=curl_init();
		curl_setopt($ch,CURLOPT_URL,$url);
		curl_setopt($ch, CURLOPT_FILE, $fp);
		curl_exec($ch);
		fclose($fp);
		$info=curl_getinfo($ch);
		$httpCode=$info['http_code'];
		curl_close($ch);
		if($httpCode==200 or $httpCode==0) {
			OC_Filesystem::fromTmpFile($tmpfile,$dir.'/'.$file);
			return true;
		}else{
			return false;
		}
	}

	/**
	 * set the maximum upload size limit for apache hosts using .htaccess
	 * @param int size filesisze in bytes
	 * @return false on failure, size on success
	 */
	static function setUploadLimit($size) {
		//don't allow user to break his config -- upper boundary
		if($size > PHP_INT_MAX) {
			//max size is always 1 byte lower than computerFileSize returns
			if($size > PHP_INT_MAX+1)
				return false;
			$size -=1;
		} else {
			$size=OC_Helper::humanFileSize($size);
			$size=substr($size,0,-1);//strip the B
			$size=str_replace(' ','',$size); //remove the space between the size and the postfix
		}

		//don't allow user to break his config -- broken or malicious size input
		if(intval($size) == 0) {
			return false;
		}

		$htaccess = @file_get_contents(OC::$SERVERROOT.'/.htaccess'); //supress errors in case we don't have permissions for
		if(!$htaccess) {
			return false;
		}

		$phpValueKeys = array(
			'upload_max_filesize',
			'post_max_size'
		);

		foreach($phpValueKeys as $key) {
		    $pattern = '/php_value '.$key.' (\S)*/';
		    $setting = 'php_value '.$key.' '.$size;
		    $hasReplaced = 0;
		    $content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced);
		    if($content !== NULL) {
				$htaccess = $content;
			}
			if($hasReplaced == 0) {
				$htaccess .= "\n" . $setting;
			}
		}

		//check for write permissions
		if(is_writable(OC::$SERVERROOT.'/.htaccess')) {
			file_put_contents(OC::$SERVERROOT.'/.htaccess', $htaccess);
			return OC_Helper::computerFileSize($size);
		} else { OC_Log::write('files','Can\'t write upload limit to '.OC::$SERVERROOT.'/.htaccess. Please check the file permissions',OC_Log::WARN); }

		return false;
	}

	/**
	 * normalize a path, removing any double, add leading /, etc
	 * @param string $path
	 * @return string
	 */
	static public function normalizePath($path) {
		$path='/'.$path;
		$old='';
		while($old!=$path) {//replace any multiplicity of slashes with a single one
			$old=$path;
			$path=str_replace('//','/',$path);
		}
		return $path;
	}
}

function fileCmp($a,$b) {
	if($a['type']=='dir' and $b['type']!='dir') {
		return -1;
	}elseif($a['type']!='dir' and $b['type']=='dir') {
		return 1;
	}else{
		return strnatcasecmp($a['name'],$b['name']);
	}
}