1 /*
2  * Hydrogen
3  * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4  *
5  * http://www.hydrogen-music.org
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY, without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 
23 #include <hydrogen/basics/drumkit.h>
24 #include <hydrogen/config.h>
25 #ifdef H2CORE_HAVE_LIBARCHIVE
26 #include <archive.h>
27 #include <archive_entry.h>
28 #else
29 #ifndef WIN32
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <zlib.h>
33 #include <libtar.h>
34 #endif
35 #endif
36 
37 #include <hydrogen/basics/sample.h>
38 #include <hydrogen/basics/drumkit_component.h>
39 #include <hydrogen/basics/instrument.h>
40 #include <hydrogen/basics/instrument_list.h>
41 #include <hydrogen/basics/instrument_component.h>
42 #include <hydrogen/basics/instrument_layer.h>
43 
44 #include <hydrogen/helpers/xml.h>
45 #include <hydrogen/helpers/filesystem.h>
46 #include <hydrogen/helpers/legacy.h>
47 
48 namespace H2Core
49 {
50 
51 const char* Drumkit::__class_name = "Drumkit";
52 
Drumkit()53 Drumkit::Drumkit() : Object( __class_name ), __samples_loaded( false ), __instruments( nullptr ), __components( nullptr )
54 {
55 	__components = new std::vector<DrumkitComponent*> ();
56 }
57 
Drumkit(Drumkit * other)58 Drumkit::Drumkit( Drumkit* other ) :
59 	Object( __class_name ),
60 	__path( other->get_path() ),
61 	__name( other->get_name() ),
62 	__author( other->get_author() ),
63 	__info( other->get_info() ),
64 	__license( other->get_license() ),
65 	__image( other->get_image() ),
66 	__imageLicense( other->get_image_license() ),
67 	__samples_loaded( other->samples_loaded() ),
68 	__components( nullptr )
69 {
70 	__instruments = new InstrumentList( other->get_instruments() );
71 
72 	__components = new std::vector<DrumkitComponent*> ();
73 	for (auto it = other->get_components()->begin(); it != other->get_components()->end(); ++it) {
74 		__components->push_back(new DrumkitComponent(*it));
75 	}
76 }
77 
~Drumkit()78 Drumkit::~Drumkit()
79 {
80 	for (std::vector<DrumkitComponent*>::iterator it = __components->begin() ; it != __components->end(); ++it) {
81 		delete *it;
82 	}
83 	delete __components;
84 
85 	if( __instruments ) {
86 		delete __instruments;
87 	}
88 }
89 
load_by_name(const QString & dk_name,const bool load_samples)90 Drumkit* Drumkit::load_by_name ( const QString& dk_name, const bool load_samples )
91 {
92 	QString dir = Filesystem::drumkit_path_search( dk_name );
93 	if ( dir.isEmpty() ) {
94 		return nullptr;
95 	}
96 
97 	return load( dir, load_samples );
98 }
99 
load(const QString & dk_dir,const bool load_samples)100 Drumkit* Drumkit::load( const QString& dk_dir, const bool load_samples )
101 {
102 	INFOLOG( QString( "Load drumkit %1" ).arg( dk_dir ) );
103 	if( !Filesystem::drumkit_valid( dk_dir ) ) {
104 		ERRORLOG( QString( "%1 is not valid drumkit" ).arg( dk_dir ) );
105 		return nullptr;
106 	}
107 	return load_file( Filesystem::drumkit_file( dk_dir ), load_samples );
108 }
109 
load_file(const QString & dk_path,const bool load_samples)110 Drumkit* Drumkit::load_file( const QString& dk_path, const bool load_samples )
111 {
112 	XMLDoc doc;
113 	if( !doc.read( dk_path, Filesystem::drumkit_xsd_path() ) ) {
114 
115 		//Something went wrong. Lets see how old this drumkit is..
116 
117 		//Do we have any components?
118 		doc.read( dk_path );
119 		auto nodeList = doc.elementsByTagName( "instrumentComponent" );
120 		if( nodeList.size() == 0 )
121 		{
122 			//No components. That drumkit seems to be quite old. Use legacy code..
123 
124 			Drumkit* pDrumkit = Legacy::load_drumkit( dk_path );
125 			upgrade_drumkit(pDrumkit, dk_path);
126 
127 			return pDrumkit;
128 		} else {
129 			//If the drumkit does not comply witht the current xsd, but has components, it may suffer from
130 			// problems with invalid values (for example float ADSR values, see #658). Lets try to load it
131 			// with our current drumkit.
132 
133 			XMLNode root = doc.firstChildElement( "drumkit_info" );
134 			if ( root.isNull() ) {
135 				ERRORLOG( "drumkit_info node not found" );
136 				return nullptr;
137 			}
138 
139 			Drumkit* pDrumkit = Drumkit::load_from( &root, dk_path.left( dk_path.lastIndexOf( "/" ) ) );
140 			upgrade_drumkit(pDrumkit, dk_path);
141 
142 			if( load_samples ){
143 				pDrumkit->load_samples();
144 			}
145 		}
146 	}
147 
148 	XMLNode root = doc.firstChildElement( "drumkit_info" );
149 	if ( root.isNull() ) {
150 		ERRORLOG( "drumkit_info node not found" );
151 		return nullptr;
152 	}
153 
154 	Drumkit* pDrumkit = Drumkit::load_from( &root, dk_path.left( dk_path.lastIndexOf( "/" ) ) );
155 	if( load_samples ){
156 		pDrumkit->load_samples();
157 	}
158 	return pDrumkit;
159 }
160 
load_from(XMLNode * node,const QString & dk_path)161 Drumkit* Drumkit::load_from( XMLNode* node, const QString& dk_path )
162 {
163 	QString drumkit_name = node->read_string( "name", "", false, false );
164 	if ( drumkit_name.isEmpty() ) {
165 		ERRORLOG( "Drumkit has no name, abort" );
166 		return nullptr;
167 	}
168 
169 	Drumkit* pDrumkit = new Drumkit();
170 	pDrumkit->__path = dk_path;
171 	pDrumkit->__name = drumkit_name;
172 	pDrumkit->__author = node->read_string( "author", "undefined author" );
173 	pDrumkit->__info = node->read_string( "info", "No information available." );
174 	pDrumkit->__license = node->read_string( "license", "undefined license" );
175 	pDrumkit->__image = node->read_string( "image", "" );
176 	pDrumkit->__imageLicense = node->read_string( "imageLicense", "undefined license" );
177 
178 	XMLNode componentListNode = node->firstChildElement( "componentList" );
179 	if ( ! componentListNode.isNull() ) {
180 		XMLNode componentNode = componentListNode.firstChildElement( "drumkitComponent" );
181 		while ( ! componentNode.isNull()  ) {
182 			int id = componentNode.read_int( "id", -1 );			// instrument id
183 			QString sName = componentNode.read_string( "name", "" );		// name
184 			float fVolume = componentNode.read_float( "volume", 1.0 );	// volume
185 			DrumkitComponent* pDrumkitComponent = new DrumkitComponent( id, sName );
186 			pDrumkitComponent->set_volume( fVolume );
187 
188 			pDrumkit->get_components()->push_back(pDrumkitComponent);
189 
190 			componentNode = componentNode.nextSiblingElement( "drumkitComponent" );
191 		}
192 	} else {
193 		WARNINGLOG( "componentList node not found" );
194 		DrumkitComponent* pDrumkitComponent = new DrumkitComponent( 0, "Main" );
195 		pDrumkit->get_components()->push_back(pDrumkitComponent);
196 	}
197 
198 	XMLNode instruments_node = node->firstChildElement( "instrumentList" );
199 	if ( instruments_node.isNull() ) {
200 		WARNINGLOG( "instrumentList node not found" );
201 		pDrumkit->set_instruments( new InstrumentList() );
202 	} else {
203 		pDrumkit->set_instruments( InstrumentList::load_from( &instruments_node, dk_path, drumkit_name ) );
204 	}
205 	return pDrumkit;
206 }
207 
load_samples()208 void Drumkit::load_samples()
209 {
210 	INFOLOG( QString( "Loading drumkit %1 instrument samples" ).arg( __name ) );
211 	if( !__samples_loaded ) {
212 		__instruments->load_samples();
213 		__samples_loaded = true;
214 	}
215 }
216 
upgrade_drumkit(Drumkit * pDrumkit,const QString & dk_path)217 void Drumkit::upgrade_drumkit(Drumkit* pDrumkit, const QString& dk_path)
218 {
219 	if(pDrumkit != nullptr)
220 	{
221 		WARNINGLOG( QString( "ugrade drumkit %1" ).arg( dk_path ) );
222 
223 		Filesystem::file_copy( dk_path,
224 		                       dk_path + ".bak",
225 		                       false /* do not overwrite existing files */ );
226 
227 		pDrumkit->save_file( dk_path, true, -1 );
228 	}
229 }
230 
unload_samples()231 void Drumkit::unload_samples()
232 {
233 	INFOLOG( QString( "Unloading drumkit %1 instrument samples" ).arg( __name ) );
234 	if( __samples_loaded ) {
235 		__instruments->unload_samples();
236 		__samples_loaded = false;
237 	}
238 }
239 
save(const QString & name,const QString & author,const QString & info,const QString & license,const QString & image,const QString & imageLicense,InstrumentList * pInstruments,std::vector<DrumkitComponent * > * pComponents,bool overwrite)240 bool Drumkit::save( const QString&					name,
241                     const QString&					author,
242                     const QString&					info,
243                     const QString&					license,
244                     const QString& 					image,
245                     const QString& 					imageLicense,
246                     InstrumentList*					pInstruments,
247                     std::vector<DrumkitComponent*>* pComponents,
248                     bool overwrite )
249 {
250 	Drumkit* pDrumkit = new Drumkit();
251 	pDrumkit->set_name( name );
252 	pDrumkit->set_author( author );
253 	pDrumkit->set_info( info );
254 	pDrumkit->set_license( license );
255 
256 	// Before storing the absolute path to the image of the drumkit it
257 	// has to be checked whether an actual path was supplied. If not,
258 	// the construction of QFileInfo will fail.
259 	if ( !image.isEmpty() ) {
260 		QFileInfo fi( image );
261 		pDrumkit->set_path( fi.absolutePath() );
262 		pDrumkit->set_image( fi.fileName() );
263 	}
264 	pDrumkit->set_image_license( imageLicense );
265 
266 	pDrumkit->set_instruments( new InstrumentList( pInstruments ) );      // FIXME: why must we do that ? there is something weird with updateInstrumentLines
267 
268 	std::vector<DrumkitComponent*>* pCopiedVector = new std::vector<DrumkitComponent*> ();
269 	for (std::vector<DrumkitComponent*>::iterator it = pComponents->begin() ; it != pComponents->end(); ++it) {
270 		DrumkitComponent* pSrcComponent = *it;
271 		pCopiedVector->push_back( new DrumkitComponent( pSrcComponent ) );
272 	}
273 	pDrumkit->set_components( pCopiedVector );
274 
275 	bool ret = pDrumkit->save( overwrite );
276 	delete pDrumkit;
277 
278 	return ret;
279 }
280 
user_drumkit_exists(const QString & name)281 bool Drumkit::user_drumkit_exists( const QString& name)
282 {
283 	return Filesystem::file_exists( Filesystem::drumkit_file( Filesystem::usr_drumkits_dir() + name ), true /*silent*/ );
284 }
285 
save(bool overwrite)286 bool Drumkit::save( bool overwrite )
287 {
288 	return  save( QString( Filesystem::usr_drumkits_dir() + __name ), overwrite );
289 }
290 
save(const QString & dk_dir,bool overwrite)291 bool Drumkit::save( const QString& dk_dir, bool overwrite )
292 {
293 	INFOLOG( QString( "Saving drumkit %1 into %2" ).arg( __name ).arg( dk_dir ) );
294 	if( !Filesystem::mkdir( dk_dir ) ) {
295 		return false;
296 	}
297 	bool ret = save_samples( dk_dir, overwrite );
298 	if ( ret ) {
299 		ret = save_file( Filesystem::drumkit_file( dk_dir ), overwrite );
300 	}
301 	return ret;
302 }
303 
save_file(const QString & dk_path,bool overwrite,int component_id)304 bool Drumkit::save_file( const QString& dk_path, bool overwrite, int component_id )
305 {
306 	INFOLOG( QString( "Saving drumkit definition into %1" ).arg( dk_path ) );
307 	if( !overwrite && Filesystem::file_exists( dk_path, true ) ) {
308 		ERRORLOG( QString( "drumkit %1 already exists" ).arg( dk_path ) );
309 		return false;
310 	}
311 	XMLDoc doc;
312 	XMLNode root = doc.set_root( "drumkit_info", "drumkit" );
313 	save_to( &root, component_id );
314 	return doc.write( dk_path );
315 }
316 
save_to(XMLNode * node,int component_id)317 void Drumkit::save_to( XMLNode* node, int component_id )
318 {
319 	node->write_string( "name", __name );
320 	node->write_string( "author", __author );
321 	node->write_string( "info", __info );
322 	node->write_string( "license", __license );
323 	node->write_string( "image", __image );
324 	node->write_string( "imageLicense", __imageLicense );
325 
326 	if( component_id == -1 ) {
327 		XMLNode components_node = node->createNode( "componentList" );
328 		for (std::vector<DrumkitComponent*>::iterator it = __components->begin() ; it != __components->end(); ++it) {
329 			DrumkitComponent* pComponent = *it;
330 			pComponent->save_to( &components_node );
331 		}
332 	}
333 	__instruments->save_to( node, component_id );
334 }
335 
save_samples(const QString & dk_dir,bool overwrite)336 bool Drumkit::save_samples( const QString& dk_dir, bool overwrite )
337 {
338 	INFOLOG( QString( "Saving drumkit %1 samples into %2" ).arg( __name ).arg( dk_dir ) );
339 	if( !Filesystem::mkdir( dk_dir ) ) {
340 		return false;
341 	}
342 
343 	InstrumentList* pInstrList = get_instruments();
344 	for( int i = 0; i < pInstrList->size(); i++ ) {
345 		Instrument* pInstrument = ( *pInstrList )[i];
346 		for (std::vector<InstrumentComponent*>::iterator it = pInstrument->get_components()->begin() ; it != pInstrument->get_components()->end(); ++it) {
347 			InstrumentComponent* pComponent = *it;
348 
349 			for ( int n = 0; n < InstrumentComponent::getMaxLayers(); n++ ) {
350 				InstrumentLayer* pLayer = pComponent->get_layer( n );
351 				if( pLayer ) {
352 					QString src = pLayer->get_sample()->get_filepath();
353 					QString dst = dk_dir + "/" + pLayer->get_sample()->get_filename();
354 
355 					if( src != dst ) {
356 						QString original_dst = dst;
357 
358 						// If the destination path does not have an extension and there is a dot in the path, hell will break loose. QFileInfo maybe?
359 						int insertPosition = original_dst.length();
360 						if( original_dst.lastIndexOf(".") > 0 ) {
361 							insertPosition = original_dst.lastIndexOf(".");
362 						}
363 
364 						if(overwrite == false) {
365 							// If the destination path already exists, try to use basename_1, basename_2, etc. instead of basename.
366 							int tries = 0;
367 							while( Filesystem::file_exists( dst, true )) {
368 								tries++;
369 								dst = original_dst;
370 								dst.insert( insertPosition, QString("_%1").arg(tries) );
371 							}
372 						}
373 
374 						pLayer->get_sample()->set_filename( dst );
375 
376 						if( !Filesystem::file_copy( src, dst ) ) {
377 							return false;
378 						}
379 					}
380 				}
381 			}
382 		}
383 	}
384 	if ( !save_image( dk_dir, overwrite ) ) {
385 		return false;
386 	}
387 
388 	return true;
389 }
390 
save_image(const QString & dk_dir,bool overwrite)391 bool Drumkit::save_image( const QString& dk_dir, bool overwrite )
392 {
393 	if ( __image.length() > 0 ) {
394 		QString src = __path + "/" + __image;
395 		QString dst = dk_dir + "/" + __image;
396 		if ( Filesystem::file_exists ( src ) ) {
397 			if( !Filesystem::file_copy( src, dst ) ) {
398 				ERRORLOG( QString( "Error copying %1 to %2").arg( src ).arg( dst ) );
399 				return false;
400 			}
401 		}
402 	}
403 	return true;
404 }
405 
set_instruments(InstrumentList * instruments)406 void Drumkit::set_instruments( InstrumentList* instruments )
407 {
408 	if( __instruments != nullptr ) {
409 		delete __instruments;
410 	}
411 
412 	__instruments = instruments;
413 }
414 
set_components(std::vector<DrumkitComponent * > * components)415 void Drumkit::set_components( std::vector<DrumkitComponent*>* components )
416 {
417 	for (std::vector<DrumkitComponent*>::iterator it = __components->begin() ; it != __components->end(); ++it) {
418 		delete *it;
419 	}
420 
421 	delete __components;
422 	__components = components;
423 }
424 
remove(const QString & dk_name)425 bool Drumkit::remove( const QString& dk_name )
426 {
427 	QString dk_dir = Filesystem::drumkit_path_search( dk_name );
428 	if( !Filesystem::drumkit_valid( dk_dir ) ) {
429 		ERRORLOG( QString( "%1 is not valid drumkit" ).arg( dk_dir ) );
430 		return false;
431 	}
432 	_INFOLOG( QString( "Removing drumkit: %1" ).arg( dk_dir ) );
433 	if( !Filesystem::rm( dk_dir, true ) ) {
434 		_ERRORLOG( QString( "Unable to remove drumkit: %1" ).arg( dk_dir ) );
435 		return false;
436 	}
437 	return true;
438 }
439 
dump()440 void Drumkit::dump()
441 {
442 	DEBUGLOG( "Drumkit dump" );
443 	DEBUGLOG( " |- Path = " + __path );
444 	DEBUGLOG( " |- Name = " + __name );
445 	DEBUGLOG( " |- Author = " + __author );
446 	DEBUGLOG( " |- Info = " + __info );
447 	DEBUGLOG( " |- Image = " + __image );
448 	DEBUGLOG( " |- Image = " + __imageLicense );
449 
450 	DEBUGLOG( " |- Instrument list" );
451 	for ( int i=0; i<__instruments->size(); i++ ) {
452 		Instrument* instrument = ( *__instruments )[i];
453 		DEBUGLOG( QString( "  |- (%1 of %2) Name = %3" )
454 		          .arg( i )
455 		          .arg( __instruments->size()-1 )
456 		          .arg( instrument->get_name() )
457 		        );
458 		for (std::vector<InstrumentComponent*>::iterator it = instrument->get_components()->begin() ; it != instrument->get_components()->end(); ++it) {
459 			InstrumentComponent* pComponent = *it;
460 
461 			for ( int j = 0; j < InstrumentComponent::getMaxLayers(); j++ ) {
462 				InstrumentLayer* pLayer = pComponent->get_layer( j );
463 				if ( pLayer ) {
464 					Sample* pSample = pLayer->get_sample();
465 					if ( pSample ) {
466 						DEBUGLOG( QString( "   |- %1 [%2]" ).arg( pSample->get_filepath() ).arg( pSample->is_empty() ) );
467 					} else {
468 						DEBUGLOG( "   |- NULL sample" );
469 					}
470 				}
471 			}
472 		}
473 	}
474 }
475 
install(const QString & path)476 bool Drumkit::install( const QString& path )
477 {
478 	_INFOLOG( QString( "Install drumkit %1" ).arg( path ) );
479 #ifdef H2CORE_HAVE_LIBARCHIVE
480 	int r;
481 	struct archive* arch;
482 	struct archive_entry* entry;
483 
484 	arch = archive_read_new();
485 
486 #if ARCHIVE_VERSION_NUMBER < 3000000
487 	archive_read_support_compression_all( arch );
488 #else
489 	archive_read_support_filter_all( arch );
490 #endif
491 
492 	archive_read_support_format_all( arch );
493 
494 #if ARCHIVE_VERSION_NUMBER < 3000000
495 	if ( ( r = archive_read_open_file( arch, path.toLocal8Bit(), 10240 ) ) ) {
496 #else
497 	if ( ( r = archive_read_open_filename( arch, path.toLocal8Bit(), 10240 ) ) ) {
498 #endif
499 		_ERRORLOG( QString( "archive_read_open_file() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
500 		archive_read_close( arch );
501 
502 #if ARCHIVE_VERSION_NUMBER < 3000000
503 		archive_read_finish( arch );
504 #else
505 		archive_read_free( arch );
506 #endif
507 
508 		return false;
509 	}
510 	bool ret = true;
511 	QString dk_dir = Filesystem::usr_drumkits_dir() + "/";
512 	while ( ( r = archive_read_next_header( arch, &entry ) ) != ARCHIVE_EOF ) {
513 		if ( r != ARCHIVE_OK ) {
514 			_ERRORLOG( QString( "archive_read_next_header() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
515 			ret = false;
516 			break;
517 		}
518 		QString np = dk_dir + archive_entry_pathname( entry );
519 
520 		QByteArray newpath = np.toLocal8Bit();
521 
522 		archive_entry_set_pathname( entry, newpath.data() );
523 		r = archive_read_extract( arch, entry, 0 );
524 		if ( r == ARCHIVE_WARN ) {
525 			_WARNINGLOG( QString( "archive_read_extract() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
526 		} else if ( r != ARCHIVE_OK ) {
527 			_ERRORLOG( QString( "archive_read_extract() [%1] %2" ).arg( archive_errno( arch ) ).arg( archive_error_string( arch ) ) );
528 			ret = false;
529 			break;
530 		}
531 	}
532 	archive_read_close( arch );
533 
534 #if ARCHIVE_VERSION_NUMBER < 3000000
535 	archive_read_finish( arch );
536 #else
537 	archive_read_free( arch );
538 #endif
539 
540 	return ret;
541 #else // H2CORE_HAVE_LIBARCHIVE
542 #ifndef WIN32
543 	// GUNZIP
544 	QString gzd_name = path.left( path.indexOf( "." ) ) + ".tar";
545 	FILE* gzd_file = fopen( gzd_name.toLocal8Bit(), "wb" );
546 	gzFile gzip_file = gzopen( path.toLocal8Bit(), "rb" );
547 	if ( !gzip_file ) {
548 		_ERRORLOG( QString( "Error reading drumkit file: %1" ).arg( path ) );
549 		gzclose( gzip_file );
550 		fclose( gzd_file );
551 		return false;
552 	}
553 	uchar buf[4096];
554 	while ( gzread( gzip_file, buf, 4096 ) > 0 ) {
555 		fwrite( buf, sizeof( uchar ), 4096, gzd_file );
556 	}
557 	gzclose( gzip_file );
558 	fclose( gzd_file );
559 	// UNTAR
560 	TAR* tar_file;
561 
562 	QByteArray tar_path = gzd_name.toLocal8Bit();
563 
564 	if ( tar_open( &tar_file, tar_path.data(), NULL, O_RDONLY, 0,  TAR_GNU ) == -1 ) {
565 		_ERRORLOG( QString( "tar_open(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
566 		return false;
567 	}
568 	bool ret = true;
569 	char dst_dir[1024];
570 	QString dk_dir = Filesystem::usr_drumkits_dir() + "/";
571 	strncpy( dst_dir, dk_dir.toLocal8Bit(), 1024 );
572 	if ( tar_extract_all( tar_file, dst_dir ) != 0 ) {
573 		_ERRORLOG( QString( "tar_extract_all(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
574 		ret = false;
575 	}
576 	if ( tar_close( tar_file ) != 0 ) {
577 		_ERRORLOG( QString( "tar_close(): %1" ).arg( QString::fromLocal8Bit( strerror( errno ) ) ) );
578 		ret = false;
579 	}
580 	return ret;
581 #else // WIN32
582 	_ERRORLOG( "WIN32 NOT IMPLEMENTED" );
583 	return false;
584 #endif
585 #endif
586 }
587 
588 };
589 
590 /* vim: set softtabstop=4 noexpandtab: */
591