xref: /haiku/src/apps/deskbar/ResourceSet.cpp (revision b617a7b410c05275effb95f4b2f5608359d9b7b9)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30 trademarks of Be Incorporated in the United States and other countries. Other
31 brand product names are registered trademarks or trademarks of their respective
32 holders.
33 All rights reserved.
34 */
35 
36 
37 #define USE_RESOURCES 1
38 
39 #include "ResourceSet.h"
40 
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <ctype.h>
44 
45 #include <Application.h>
46 #include <Autolock.h>
47 #include <Bitmap.h>
48 #include <DataIO.h>
49 #include <Debug.h>
50 #include <Entry.h>
51 #include <File.h>
52 #include <Path.h>
53 #include <String.h>
54 
55 #if USE_RESOURCES
56 #include <Resources.h>
57 #endif
58 
59 #if USE_RESOURCES
60 #define RESOURCES_ONLY(x) x
61 #else
62 #define RESOURCES_ONLY(x)
63 #endif
64 
65 
66 namespace TResourcePrivate {
67 	class TypeObject {
68 	public:
69 		TypeObject()
70 			:	fDeleteOK(false)
71 		{
72 		}
73 
74 		virtual ~TypeObject()
75 		{
76 			if (!fDeleteOK)
77 				debugger("deleting object owned by BResourceSet");
78 		}
79 
80 		void Delete()
81 		{
82 			fDeleteOK = true;
83 		}
84 
85 	private:
86 		TypeObject(const TypeObject &);
87 		TypeObject &operator=(const TypeObject &);
88 		bool operator==(const TypeObject &);
89 		bool operator!=(const TypeObject &);
90 
91 		bool fDeleteOK;
92 	};
93 
94 	class BitmapTypeItem : public BBitmap, public TypeObject {
95 	public:
96 		BitmapTypeItem(BRect bounds, uint32 flags, color_space depth,
97 			int32 bytesPerRow = B_ANY_BYTES_PER_ROW, screen_id screenID
98 			= B_MAIN_SCREEN_ID)
99 			:	BBitmap(bounds, flags, depth, bytesPerRow, screenID)
100 		{
101 		}
102 
103 		BitmapTypeItem(const BBitmap* source, bool accepts_views = false,
104 			bool need_contiguous = false)
105 			:	BBitmap(source, accepts_views, need_contiguous)
106 		{
107 		}
108 
109 		BitmapTypeItem(BMessage* data)
110 			:	BBitmap(data)
111 		{
112 		}
113 
114 		virtual ~BitmapTypeItem()
115 		{
116 		}
117 	};
118 
119 	class StringBlockTypeItem : public TStringBlock, public TypeObject {
120 	public:
121 		StringBlockTypeItem(BDataIO* data)
122 			:	TStringBlock(data)
123 		{
124 		}
125 
126 		StringBlockTypeItem(const void* block, size_t size)
127 			:	TStringBlock(block, size)
128 		{
129 		}
130 
131 		virtual ~StringBlockTypeItem()
132 		{
133 		}
134 	};
135 
136 	class TypeItem {
137 	public:
138 		TypeItem(int32 id, const char* name, const void* data, size_t size)
139 			:	fID(id), fName(name),
140 				fData(const_cast<void*>(data)), fSize(size), fObject(0),
141 				fOwnData(false), fSourceIsFile(false)
142 		{
143 		}
144 
145 		TypeItem(int32 id, const char* name, BFile* file)
146 			:	fID(id),
147 				fName(name),
148 				fData(NULL),
149 				fSize(0),
150 				fObject(NULL),
151 				fOwnData(true),
152 				fSourceIsFile(false)
153 		{
154 			off_t size;
155 			if (file->GetSize(&size) == B_OK) {
156 				fSize = (size_t)size;
157 				fData = malloc(fSize);
158 				if (file->ReadAt(0, fData, fSize) < B_OK ) {
159 					free(fData);
160 					fData = NULL;
161 					fSize = 0;
162 				}
163 			}
164 		}
165 
166 		virtual ~TypeItem()
167 		{
168 			if (fOwnData) {
169 				free(fData);
170 				fData = NULL;
171 				fSize = 0;
172 			}
173 			SetObject(NULL);
174 		}
175 
176 		int32 ID() const
177 		{
178 			return fID;
179 		}
180 
181 		const char* Name() const
182 		{
183 			return fName.String();
184 		}
185 
186 		const void* Data() const
187 		{
188 			return fData;
189 		}
190 
191 		size_t Size() const
192 		{
193 			return fSize;
194 		}
195 
196 		void SetObject(TypeObject* object)
197 		{
198 			if (object == fObject)
199 				return;
200 			if (fObject)
201 				fObject->Delete();
202 			fObject = object;
203 		}
204 
205 		TypeObject* Object() const
206 		{
207 			return fObject;
208 		}
209 
210 		void SetSourceIsFile(bool state)
211 		{
212 			fSourceIsFile = state;
213 		}
214 
215 		bool SourceIsFile() const
216 		{
217 			return fSourceIsFile;
218 		}
219 
220 	private:
221 		int32 fID;
222 		BString fName;
223 		void* fData;
224 		size_t fSize;
225 		TypeObject* fObject;
226 		bool fOwnData;
227 		bool fSourceIsFile;
228 	};
229 
230 	static bool FreeTypeItemFunc(void* item)
231 	{
232 		delete reinterpret_cast<TypeItem*>(item);
233 		return false;
234 	}
235 
236 	class TypeList {
237 	public:
238 		TypeList(type_code type)
239 			:	fType(type)
240 		{
241 		}
242 
243 		virtual ~TypeList()
244 		{
245 			fItems.DoForEach(FreeTypeItemFunc);
246 			fItems.MakeEmpty();
247 		}
248 
249 		type_code Type() const
250 		{
251 			return fType;
252 		}
253 
254 		TypeItem* FindItemByID(int32 id)
255 		{
256 			for (int32 i = fItems.CountItems() - 1; i >= 0; i--) {
257 				TypeItem* it = (TypeItem*)fItems.ItemAt(i);
258 				if (it->ID() == id)
259 					return it;
260 			}
261 			return NULL;
262 		}
263 
264 		TypeItem* FindItemByName(const char* name)
265 		{
266 			for (int32 i = fItems.CountItems() - 1; i >= 0; i--) {
267 				TypeItem* it = (TypeItem*)fItems.ItemAt(i);
268 				if (strcmp(it->Name(), name) == 0)
269 					return it;
270 			}
271 			return NULL;
272 		}
273 
274 		void AddItem(TypeItem* item)
275 		{
276 			fItems.AddItem(item);
277 		}
278 
279 	private:
280 		type_code fType;
281 		BList fItems;
282 	};
283 }
284 
285 using namespace TResourcePrivate;
286 
287 //	#pragma mark -
288 // ----------------------------- TStringBlock -----------------------------
289 
290 
291 TStringBlock::TStringBlock(BDataIO* data)
292 	:	fNumEntries(0),
293 		fIndex(0),
294 		fStrings(NULL),
295 		fOwnData(true)
296 {
297 	fStrings = (char*)malloc(1024);
298 	size_t pos = 0;
299 	ssize_t amount;
300 	while ((amount = data->Read(fStrings + pos, 1024)) == 1024) {
301 		pos += amount;
302 		fStrings = (char*)realloc(fStrings, pos + 1024);
303 	}
304 	if (amount > 0)
305 		pos += amount;
306 
307 	fNumEntries = PreIndex(fStrings, amount);
308 	fIndex = (size_t*)malloc(sizeof(size_t) * fNumEntries);
309 	MakeIndex(fStrings, amount, fNumEntries, fIndex);
310 }
311 
312 
313 TStringBlock::TStringBlock(const void* block, size_t size)
314 	:
315 	fNumEntries(0),
316 	fIndex(0),
317 	fStrings(NULL),
318 	fOwnData(false)
319 {
320 	fIndex = (size_t*)const_cast<void*>(block);
321 	fStrings = (char*)const_cast<void*>(block);
322 
323 	// Figure out how many entries there are.
324 	size_t last_off = 0;
325 	while (fIndex[fNumEntries] > last_off && fIndex[fNumEntries] < size ) {
326 		last_off = fIndex[fNumEntries];
327 		fNumEntries++;
328 	}
329 }
330 
331 
332 TStringBlock::~TStringBlock()
333 {
334 	if (fOwnData) {
335 		free(fIndex);
336 		free(fStrings);
337 	}
338 	fIndex = 0;
339 	fStrings = NULL;
340 }
341 
342 
343 const char*
344 TStringBlock::String(size_t index) const
345 {
346 	if (index >= fNumEntries)
347 		return NULL;
348 
349 	return fStrings + fIndex[index];
350 }
351 
352 
353 size_t
354 TStringBlock::PreIndex(char* strings, ssize_t len)
355 {
356 	size_t count = 0;
357 	char* orig = strings;
358 	char* end = strings + len;
359 	bool in_cr = false;
360 	bool first = true;
361 	bool skipping = false;
362 
363 	while (orig < end) {
364 		if (*orig == '\n' || *orig == '\r' || *orig == 0) {
365 			if (!in_cr && *orig == '\r')
366 				in_cr = true;
367 			if (in_cr && *orig == '\n') {
368 				orig++;
369 				continue;
370 			}
371 			first = true;
372 			skipping = false;
373 			*strings = 0;
374 			count++;
375 		} else if (first && *orig == '#') {
376 			skipping = true;
377 			first = false;
378 			orig++;
379 			continue;
380 		} else if (skipping) {
381 			orig++;
382 			continue;
383 		} else if (*orig == '\\' && (orig + 1) < end) {
384 			orig++;
385 			switch (*orig) {
386 				case '\\':
387 					*strings = '\\';
388 					break;
389 
390 				case '\n':
391 					*strings = '\n';
392 					break;
393 
394 				case '\r':
395 					*strings = '\r';
396 					break;
397 
398 				case '\t':
399 					*strings = '\t';
400 					break;
401 
402 				default:
403 					*strings = *orig;
404 					break;
405 			}
406 		} else
407 			*strings = *orig;
408 
409 		orig++;
410 		strings++;
411 	}
412 	return count;
413 }
414 
415 
416 void
417 TStringBlock::MakeIndex(const char* strings, ssize_t len,
418 	size_t indexLength, size_t* resultingIndex)
419 {
420 	*resultingIndex++ = 0;
421 	indexLength--;
422 
423 	ssize_t pos = 0;
424 	while (pos < len && indexLength > 0) {
425 		if (strings[pos] == 0 ) {
426 			*resultingIndex++ = (size_t)pos + 1;
427 			indexLength--;
428 		}
429 		pos++;
430 	}
431 }
432 
433 
434 //	#pragma mark -
435 
436 
437 #if USE_RESOURCES
438 static bool
439 FreeResourcesFunc(void* item)
440 {
441 	delete reinterpret_cast<BResources*>(item);
442 	return false;
443 }
444 #endif
445 
446 
447 static bool
448 FreePathFunc(void* item)
449 {
450 	delete reinterpret_cast<BPath*>(item);
451 	return false;
452 }
453 
454 
455 static bool
456 FreeTypeFunc(void* item)
457 {
458 	delete reinterpret_cast<TypeList*>(item);
459 	return false;
460 }
461 
462 
463 //	#pragma mark -
464 // ----------------------------- TResourceSet -----------------------------
465 
466 
467 TResourceSet::TResourceSet()
468 {
469 }
470 
471 
472 TResourceSet::~TResourceSet()
473 {
474 	BAutolock lock(&fLock);
475 #if USE_RESOURCES
476 	fResources.DoForEach(FreeResourcesFunc);
477 	fResources.MakeEmpty();
478 #endif
479 	fDirectories.DoForEach(FreePathFunc);
480 	fDirectories.MakeEmpty();
481 	fTypes.DoForEach(FreeTypeFunc);
482 	fTypes.MakeEmpty();
483 }
484 
485 
486 status_t
487 TResourceSet::AddResources(BResources* RESOURCES_ONLY(resources))
488 {
489 #if USE_RESOURCES
490 	if (!resources)
491 		return B_BAD_VALUE;
492 
493 	BAutolock lock(&fLock);
494 	status_t err = fResources.AddItem(resources) ? B_OK : B_ERROR;
495 	if (err != B_OK)
496 		delete resources;
497 	return err;
498 #else
499 	return B_ERROR;
500 #endif
501 }
502 
503 
504 status_t
505 TResourceSet::AddDirectory(const char* fullPath)
506 {
507 	if (!fullPath)
508 		return B_BAD_VALUE;
509 
510 	BPath* path = new BPath(fullPath);
511 	status_t err = path->InitCheck();
512 	if (err != B_OK) {
513 		delete path;
514 		return err;
515 	}
516 
517 	BAutolock lock(&fLock);
518 	err = fDirectories.AddItem(path) ? B_OK : B_ERROR;
519 	if (err != B_OK)
520 		delete path;
521 
522 	return err;
523 }
524 
525 
526 status_t
527 TResourceSet::AddEnvDirectory(const char* in, const char* defaultValue)
528 {
529 	BString buf;
530 	status_t err = ExpandString(&buf, in);
531 
532 	if (err != B_OK) {
533 		if (defaultValue)
534 			return AddDirectory(defaultValue);
535 		return err;
536 	}
537 
538 	return AddDirectory(buf.String());
539 }
540 
541 
542 status_t
543 TResourceSet::ExpandString(BString* out, const char* in)
544 {
545 	const char* start = in;
546 
547 	while (*in) {
548 		if (*in == '$') {
549 			if (start < in)
550 				out->Append(start, (int32)(in - start));
551 
552 			in++;
553 			char variableName[1024];
554 			size_t i = 0;
555 			if (*in == '{') {
556 				in++;
557 				while (*in && *in != '}' && i < sizeof(variableName) - 1)
558 					variableName[i++] = *in++;
559 
560 				if (*in)
561 					in++;
562 
563 			} else {
564 				while ((isalnum(*in) || *in == '_')
565 					&& i < sizeof(variableName) - 1)
566 					variableName[i++] = *in++;
567 			}
568 
569 			start = in;
570 			variableName[i] = '\0';
571 
572 			const char* val = getenv(variableName);
573 			if (!val) {
574 				PRINT(("Error: env var %s not found.\n", &variableName[0]));
575 				return B_NAME_NOT_FOUND;
576 			}
577 
578 			status_t err = ExpandString(out, val);
579 			if (err != B_OK)
580 				return err;
581 
582 		} else if (*in == '\\') {
583 			if (start < in)
584 				out->Append(start, (int32)(in - start));
585 			in++;
586 			start = in;
587 			in++;
588 		} else
589 			in++;
590 	}
591 
592 	if (start < in)
593 		out->Append(start, (int32)(in - start));
594 
595 	return B_OK;
596 }
597 
598 
599 const void*
600 TResourceSet::FindResource(type_code type, int32 id, size_t* outSize)
601 {
602 	TypeItem* item = FindItemID(type, id);
603 
604 	if (outSize)
605 		*outSize = item ? item->Size() : 0;
606 
607 	return item ? item->Data() : NULL;
608 }
609 
610 
611 const void*
612 TResourceSet::FindResource(type_code type, const char* name, size_t* outSize)
613 {
614 	TypeItem* item = FindItemName(type, name);
615 
616 	if (outSize)
617 		*outSize = item ? item->Size() : 0;
618 
619 	return item ? item->Data() : NULL;
620 }
621 
622 
623 const BBitmap*
624 TResourceSet::FindBitmap(type_code type, int32 id)
625 {
626 	return ReturnBitmapItem(type, FindItemID(type, id));
627 }
628 
629 
630 const BBitmap*
631 TResourceSet::FindBitmap(type_code type, const char* name)
632 {
633 	return ReturnBitmapItem(type, FindItemName(type, name));
634 }
635 
636 
637 const TStringBlock*
638 TResourceSet::FindStringBlock(type_code type, int32 id)
639 {
640 	return ReturnStringBlockItem(FindItemID(type, id));
641 }
642 
643 
644 const TStringBlock*
645 TResourceSet::FindStringBlock(type_code type, const char* name)
646 {
647 	return ReturnStringBlockItem(FindItemName(type, name));
648 }
649 
650 
651 const char*
652 TResourceSet::FindString(type_code type, int32 id, uint32 index)
653 {
654 	const TStringBlock* stringBlock = FindStringBlock(type, id);
655 
656 	if (!stringBlock)
657 		return NULL;
658 
659 	return stringBlock->String(index);
660 }
661 
662 
663 const char*
664 TResourceSet::FindString(type_code type, const char* name, uint32 index)
665 {
666 	const TStringBlock* stringBlock = FindStringBlock(type, name);
667 
668 	if (!stringBlock)
669 		return NULL;
670 
671 	return stringBlock->String(index);
672 }
673 
674 
675 TypeList*
676 TResourceSet::FindTypeList(type_code type)
677 {
678 	BAutolock lock(&fLock);
679 
680 	for (int32 i = fTypes.CountItems() - 1; i >= 0; i--) {
681 		TypeList* list = (TypeList*)fTypes.ItemAt(i);
682 		if (list && list->Type() == type)
683 			return list;
684 	}
685 
686 	return NULL;
687 }
688 
689 
690 TypeItem*
691 TResourceSet::FindItemID(type_code type, int32 id)
692 {
693 	TypeList* list = FindTypeList(type);
694 	TypeItem* item = NULL;
695 
696 	if (list)
697 		item = list->FindItemByID(id);
698 
699 	if (!item)
700 		item = LoadResource(type, id, 0, &list);
701 
702 	return item;
703 }
704 
705 
706 TypeItem*
707 TResourceSet::FindItemName(type_code type, const char* name)
708 {
709 	TypeList* list = FindTypeList(type);
710 	TypeItem* item = NULL;
711 
712 	if (list)
713 		item = list->FindItemByName(name);
714 
715 	if (!item)
716 		item = LoadResource(type, -1, name, &list);
717 
718 	return item;
719 }
720 
721 
722 TypeItem*
723 TResourceSet::LoadResource(type_code type, int32 id, const char* name,
724 	TypeList** inOutList)
725 {
726 	TypeItem* item = NULL;
727 
728 	if (name) {
729 		BEntry entry;
730 
731 		// If a named resource, first look in directories.
732 		fLock.Lock();
733 		for (int32 i = fDirectories.CountItems() - 1; i >= 0; i--) {
734 			BPath* dir = (BPath*)fDirectories.ItemAt(i);
735 			if (dir) {
736 				fLock.Unlock();
737 				BPath path(dir->Path(), name);
738 				if (entry.SetTo(path.Path(), true) == B_OK ) {
739 					BFile file(&entry, B_READ_ONLY);
740 					if (file.InitCheck() == B_OK ) {
741 						item = new TypeItem(id, name, &file);
742 						item->SetSourceIsFile(true);
743 					}
744 				}
745 				fLock.Lock();
746 			}
747 		}
748 		fLock.Unlock();
749 	}
750 
751 #if USE_RESOURCES
752 	if (!item) {
753 		// Look through resource objects for data.
754 		fLock.Lock();
755 		for (int32 i = fResources.CountItems() - 1; i >= 0; i--) {
756 			BResources* resource = (BResources*)fResources.ItemAt(i);
757 			if (resource) {
758 				const void* data = NULL;
759 				size_t size = 0;
760 				if (id >= 0)
761 					data = resource->LoadResource(type, id, &size);
762 				else if (name != NULL)
763 					data = resource->LoadResource(type, name, &size);
764 
765 				if (data && size) {
766 					item = new TypeItem(id, name, data, size);
767 					item->SetSourceIsFile(false);
768 				}
769 			}
770 		}
771 		fLock.Unlock();
772 	}
773 #endif
774 
775 	if (item) {
776 		TypeList* list = inOutList ? *inOutList : NULL;
777 		if (!list) {
778 			// Don't currently have a list for this type -- check if there is
779 			// already one.
780 			list = FindTypeList(type);
781 		}
782 
783 		BAutolock lock(&fLock);
784 
785 		if (!list) {
786 			// Need to make a new list for this type.
787 			list = new TypeList(type);
788 			fTypes.AddItem(list);
789 		}
790 		if (inOutList)
791 			*inOutList = list;
792 
793 		list->AddItem(item);
794 	}
795 
796 	return item;
797 }
798 
799 
800 BBitmap*
801 TResourceSet::ReturnBitmapItem(type_code, TypeItem* from)
802 {
803 	if (!from)
804 		return NULL;
805 
806 	TypeObject* obj = from->Object();
807 	BitmapTypeItem* bitmap = dynamic_cast<BitmapTypeItem*>(obj);
808 	if (bitmap)
809 		return bitmap;
810 
811 	// Can't change an existing object.
812 	if (obj)
813 		return NULL;
814 
815 	// Don't have a bitmap in the item -- we'll try to create one.
816 	BMemoryIO stream(from->Data(), from->Size());
817 
818 	// Try to read as an archived bitmap.
819 	stream.Seek(0, SEEK_SET);
820 	BMessage archive;
821 	if (archive.Unflatten(&stream) == B_OK) {
822 		bitmap = new BitmapTypeItem(&archive);
823 		if (bitmap && bitmap->InitCheck() != B_OK) {
824 			bitmap->Delete();
825 				// allows us to delete this bitmap...
826 			delete bitmap;
827 			bitmap = NULL;
828 		}
829 	}
830 
831 	if (bitmap) {
832 		BAutolock lock(&fLock);
833 		if (from->Object() != NULL) {
834 			// Whoops! Someone snuck in under us.
835 			bitmap->Delete();
836 			delete bitmap;
837 			bitmap = dynamic_cast<BitmapTypeItem*>(from->Object());
838 		} else
839 			from->SetObject(bitmap);
840 	}
841 
842 	return bitmap;
843 }
844 
845 
846 TStringBlock*
847 TResourceSet::ReturnStringBlockItem(TypeItem* from)
848 {
849 	if (!from)
850 		return NULL;
851 
852 	TypeObject* obj = from->Object();
853 	StringBlockTypeItem* stringBlock = dynamic_cast<StringBlockTypeItem*>(obj);
854 	if (stringBlock)
855 		return stringBlock;
856 
857 	// Can't change an existing object.
858 	if (obj)
859 		return NULL;
860 
861 	// Don't have a string block in the item -- we'll create one.
862 	if (from->SourceIsFile() ) {
863 		BMemoryIO stream(from->Data(), from->Size());
864 		stringBlock = new StringBlockTypeItem(&stream);
865 	} else
866 		stringBlock = new StringBlockTypeItem(from->Data(), from->Size());
867 
868 	if (stringBlock) {
869 		BAutolock lock(&fLock);
870 		if (from->Object() != NULL) {
871 			// Whoops! Someone snuck in under us.
872 			delete stringBlock;
873 			stringBlock = dynamic_cast<StringBlockTypeItem*>(from->Object());
874 		} else
875 			from->SetObject(stringBlock);
876 	}
877 
878 	return stringBlock;
879 }
880 
881 
882 //	#pragma mark -
883 
884 
885 namespace TResourcePrivate {
886 	TResourceSet* gResources = NULL;
887 	BLocker gResourceLocker;
888 }
889 
890 
891 TResourceSet*
892 AppResSet()
893 {
894 	// If already have it, return immediately.
895 	if (gResources)
896 		return gResources;
897 
898 	// Don't have 'em, lock access to make 'em.
899 	if (!gResourceLocker.Lock())
900 		return NULL;
901 	if (gResources) {
902 		// Whoops, somebody else already did.  Oh well.
903 		gResourceLocker.Unlock();
904 		return gResources;
905 	}
906 
907 	// Make 'em.
908 	gResources = new TResourceSet;
909 	gResources->AddResources(BApplication::AppResources());
910 	gResourceLocker.Unlock();
911 	return gResources;
912 }
913