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