VideoConsumer.cpp revision 859c1f01
1//	Copyright (c) 1998-99, Be Incorporated, All Rights Reserved.
2//	SMS
3//	VideoConsumer.cpp
4
5#include <View.h>
6#include <stdio.h>
7#include <fcntl.h>
8#include <Buffer.h>
9#include <unistd.h>
10#include <string.h>
11#include <NodeInfo.h>
12#include <scheduler.h>
13#include <TimeSource.h>
14#include <StringView.h>
15#include <MediaRoster.h>
16#include <Application.h>
17#include <BufferGroup.h>
18
19#include "FtpClient.h"
20#include "VideoConsumer.h"
21
22#define M1 ((double)1000000.0)
23#define JITTER		20000
24
25#define	FUNCTION	printf
26#define ERROR		printf
27#define PROGRESS	printf
28#define LOOP		printf
29
30static status_t SetFileType(BFile * file,  int32 translator, uint32 type);
31
32const media_raw_video_format vid_format = { 29.97,1,0,239,B_VIDEO_TOP_LEFT_RIGHT,1,1,{B_RGB16,320,240,320*4,0,0}};
33
34//---------------------------------------------------------------
35
36VideoConsumer::VideoConsumer(
37	const char * name,
38	BView *view,
39	BStringView	* statusLine,
40	BMediaAddOn *addon,
41	const uint32 internal_id) :
42
43	BMediaNode(name),
44	BMediaEventLooper(),
45	BBufferConsumer(B_MEDIA_RAW_VIDEO),
46	mStatusLine(statusLine),
47	mInternalID(internal_id),
48	mAddOn(addon),
49	mConnectionActive(false),
50	mMyLatency(20000),
51	mWindow(NULL),
52	mView(view),
53	mOurBuffers(false),
54	mBuffers(NULL),
55	mTimeToFtp(false),
56	mFtpComplete(true),
57	mRate(1000000),
58        mImageFormat(0),
59        mTranslator(0),
60        mPassiveFtp(true)
61{
62	FUNCTION("VideoConsumer::VideoConsumer\n");
63
64	AddNodeKind(B_PHYSICAL_OUTPUT);
65	SetEventLatency(0);
66	mWindow = mView->Window();
67
68	for (uint32 j = 0; j < 3; j++)
69	{
70		mBitmap[j] = NULL;
71		mBufferMap[j] = 0;
72	}
73
74	strcpy(mFileNameText, "");
75	strcpy(mServerText, "");
76	strcpy(mLoginText, "");
77	strcpy(mPasswordText, "");
78	strcpy(mDirectoryText, "");
79
80	SetPriority(B_DISPLAY_PRIORITY);
81}
82
83//---------------------------------------------------------------
84
85VideoConsumer::~VideoConsumer()
86{
87	FUNCTION("VideoConsumer::~VideoConsumer\n");
88
89	Quit();
90
91	if (mWindow)
92	{
93		printf("Locking the window\n");
94		if (mWindow->Lock())
95		{
96			printf("Closing the window\n");
97			mWindow->Close();
98			mWindow = 0;
99		}
100	}
101
102	// clean up ftp thread
103	// wait up to 30 seconds if ftp is in progress
104	int32 count = 0;
105	while (!mFtpComplete && (count < 30))
106	{
107		snooze(1000000);
108		count++;
109	}
110
111	if(count == 30)
112		kill_thread(mFtpThread);
113
114	DeleteBuffers();
115
116
117}
118
119/********************************
120	From BMediaNode
121********************************/
122
123//---------------------------------------------------------------
124
125BMediaAddOn *
126VideoConsumer::AddOn(long *cookie) const
127{
128	FUNCTION("VideoConsumer::AddOn\n");
129	// do the right thing if we're ever used with an add-on
130	*cookie = mInternalID;
131	return mAddOn;
132}
133
134//---------------------------------------------------------------
135
136// This implementation is required to get around a bug in
137// the ppc compiler.
138
139void
140VideoConsumer::Start(bigtime_t performance_time)
141{
142	BMediaEventLooper::Start(performance_time);
143}
144
145void
146VideoConsumer::Stop(bigtime_t performance_time, bool immediate)
147{
148	BMediaEventLooper::Stop(performance_time, immediate);
149}
150
151void
152VideoConsumer::Seek(bigtime_t media_time, bigtime_t performance_time)
153{
154	BMediaEventLooper::Seek(media_time, performance_time);
155}
156
157void
158VideoConsumer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time)
159{
160	BMediaEventLooper::TimeWarp(at_real_time, to_performance_time);
161}
162
163status_t
164VideoConsumer::DeleteHook(BMediaNode *node)
165{
166	return BMediaEventLooper::DeleteHook(node);
167}
168
169//---------------------------------------------------------------
170
171void
172VideoConsumer::NodeRegistered()
173{
174	FUNCTION("VideoConsumer::NodeRegistered\n");
175	mIn.destination.port = ControlPort();
176	mIn.destination.id = 0;
177	mIn.source = media_source::null;
178	mIn.format.type = B_MEDIA_RAW_VIDEO;
179	mIn.format.u.raw_video = vid_format;
180
181	Run();
182}
183
184//---------------------------------------------------------------
185
186status_t
187VideoConsumer::RequestCompleted(const media_request_info & info)
188{
189	FUNCTION("VideoConsumer::RequestCompleted\n");
190	switch(info.what)
191	{
192		case media_request_info::B_SET_OUTPUT_BUFFERS_FOR:
193			{
194				if (info.status != B_OK)
195					ERROR("VideoConsumer::RequestCompleted: Not using our buffers!\n");
196			}
197			break;
198		default:
199			ERROR("VideoConsumer::RequestCompleted: Invalid argument\n");
200			break;
201	}
202	return B_OK;
203}
204
205//---------------------------------------------------------------
206
207status_t
208VideoConsumer::HandleMessage(int32 message, const void * data, size_t size)
209{
210	//FUNCTION("VideoConsumer::HandleMessage\n");
211	ftp_msg_info *info = (ftp_msg_info *)data;
212	status_t status = B_OK;
213
214	switch (message)
215	{
216		case FTP_INFO:
217			PROGRESS("VideoConsumer::HandleMessage - FTP_INFO message\n");
218			mRate = info->rate;
219			mImageFormat = info->imageFormat;
220			mTranslator = info->translator;
221			mPassiveFtp = info->passiveFtp;
222			strcpy(mFileNameText, info->fileNameText);
223			strcpy(mServerText, info->serverText);
224			strcpy(mLoginText, info->loginText);
225			strcpy(mPasswordText, info->passwordText);
226			strcpy(mDirectoryText, info->directoryText);
227			// remove old user events
228			EventQueue()->FlushEvents(TimeSource()->Now(), BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_USER_EVENT);
229			if (mRate != B_INFINITE_TIMEOUT) {
230				// if rate is not "Never," push an event to restart captures 5 seconds from now
231				media_timed_event event(TimeSource()->Now() + 5000000, BTimedEventQueue::B_USER_EVENT);
232				EventQueue()->AddEvent(event);
233			}
234			break;
235	}
236
237	return status;
238}
239
240//---------------------------------------------------------------
241
242void
243VideoConsumer::BufferReceived(BBuffer * buffer)
244{
245	LOOP("VideoConsumer::Buffer #%ld received\n", buffer->ID());
246
247	if (RunState() == B_STOPPED)
248	{
249		buffer->Recycle();
250		return;
251	}
252
253	media_timed_event event(buffer->Header()->start_time, BTimedEventQueue::B_HANDLE_BUFFER,
254						buffer, BTimedEventQueue::B_RECYCLE_BUFFER);
255	EventQueue()->AddEvent(event);
256}
257
258
259//---------------------------------------------------------------
260
261void
262VideoConsumer::ProducerDataStatus(
263	const media_destination &for_whom,
264	int32 status,
265	bigtime_t at_media_time)
266{
267	FUNCTION("VideoConsumer::ProducerDataStatus\n");
268
269	if (for_whom != mIn.destination)
270		return;
271}
272
273//---------------------------------------------------------------
274
275status_t
276VideoConsumer::CreateBuffers(
277	const media_format & with_format)
278{
279	FUNCTION("VideoConsumer::CreateBuffers\n");
280
281	// delete any old buffers
282	DeleteBuffers();
283
284	status_t status = B_OK;
285
286	// create a buffer group
287	uint32 mXSize = with_format.u.raw_video.display.line_width;
288	uint32 mYSize = with_format.u.raw_video.display.line_count;
289	color_space mColorspace = with_format.u.raw_video.display.format;
290	PROGRESS("VideoConsumer::CreateBuffers - Colorspace = %d\n", mColorspace);
291
292	mBuffers = new BBufferGroup();
293	status = mBuffers->InitCheck();
294	if (B_OK != status)
295	{
296		ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n");
297		return status;
298	}
299	// and attach the  bitmaps to the buffer group
300	for (uint32 j=0; j < 3; j++)
301	{
302		mBitmap[j] = new BBitmap(BRect(0, 0, (mXSize-1), (mYSize - 1)), mColorspace, false, true);
303		if (mBitmap[j]->IsValid())
304		{
305			buffer_clone_info info;
306			if ((info.area = area_for(mBitmap[j]->Bits())) == B_ERROR)
307				ERROR("VideoConsumer::CreateBuffers - ERROR IN AREA_FOR\n");;
308			info.offset = 0;
309			info.size = (size_t)mBitmap[j]->BitsLength();
310			info.flags = j;
311			info.buffer = 0;
312
313			if ((status = mBuffers->AddBuffer(info)) != B_OK)
314			{
315				ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER TO GROUP\n");
316				return status;
317			} else PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD BUFFER TO GROUP\n");
318		}
319		else
320		{
321			ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING BUFFER: %08lx\n", status);
322			return B_ERROR;
323		}
324	}
325
326	BBuffer ** buffList = new BBuffer * [3];
327	for (int j = 0; j < 3; j++) buffList[j] = 0;
328
329	if ((status = mBuffers->GetBufferList(3, buffList)) == B_OK)
330		for (int j = 0; j < 3; j++)
331			if (buffList[j] != NULL)
332			{
333				mBufferMap[j] = (uint32) buffList[j];
334				PROGRESS(" j = %d buffer = %08lx\n", j, mBufferMap[j]);
335			}
336			else
337			{
338				ERROR("VideoConsumer::CreateBuffers ERROR MAPPING RING BUFFER\n");
339				return B_ERROR;
340			}
341	else
342		ERROR("VideoConsumer::CreateBuffers ERROR IN GET BUFFER LIST\n");
343
344	FUNCTION("VideoConsumer::CreateBuffers - EXIT\n");
345	return status;
346}
347
348//---------------------------------------------------------------
349
350void
351VideoConsumer::DeleteBuffers()
352{
353	FUNCTION("VideoConsumer::DeleteBuffers\n");
354
355	if (mBuffers)
356	{
357		delete mBuffers;
358		mBuffers = NULL;
359
360		for (uint32 j = 0; j < 3; j++)
361			if (mBitmap[j]->IsValid())
362			{
363				delete mBitmap[j];
364				mBitmap[j] = NULL;
365			}
366	}
367	FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n");
368}
369
370//---------------------------------------------------------------
371
372status_t
373VideoConsumer::Connected(
374	const media_source & producer,
375	const media_destination & where,
376	const media_format & with_format,
377	media_input * out_input)
378{
379	FUNCTION("VideoConsumer::Connected\n");
380
381	mIn.source = producer;
382	mIn.format = with_format;
383	mIn.node = Node();
384	sprintf(mIn.name, "Video Consumer");
385	*out_input = mIn;
386
387	uint32 user_data = 0;
388	int32 change_tag = 1;
389	if (CreateBuffers(with_format) == B_OK)
390		BBufferConsumer::SetOutputBuffersFor(producer, mDestination,
391											mBuffers, (void *)&user_data, &change_tag, true);
392	else
393	{
394		ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n");
395		return B_ERROR;
396	}
397
398	mFtpBitmap = new BBitmap(BRect(0, 0, 320-1, 240-1), B_RGB32, false, false);
399	mConnectionActive = true;
400
401	FUNCTION("VideoConsumer::Connected - EXIT\n");
402	return B_OK;
403}
404
405//---------------------------------------------------------------
406
407void
408VideoConsumer::Disconnected(
409	const media_source & producer,
410	const media_destination & where)
411{
412	FUNCTION("VideoConsumer::Disconnected\n");
413
414	if (where == mIn.destination && producer == mIn.source)
415	{
416		// disconnect the connection
417		mIn.source = media_source::null;
418		delete mFtpBitmap;
419		mConnectionActive = false;
420	}
421
422}
423
424//---------------------------------------------------------------
425
426status_t
427VideoConsumer::AcceptFormat(
428	const media_destination & dest,
429	media_format * format)
430{
431	FUNCTION("VideoConsumer::AcceptFormat\n");
432
433	if (dest != mIn.destination)
434	{
435		ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n");
436		return B_MEDIA_BAD_DESTINATION;
437	}
438
439	if (format->type == B_MEDIA_NO_TYPE)
440		format->type = B_MEDIA_RAW_VIDEO;
441
442	if (format->type != B_MEDIA_RAW_VIDEO)
443	{
444		ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n");
445		return B_MEDIA_BAD_FORMAT;
446	}
447
448	if (format->u.raw_video.display.format != B_RGB32 &&
449		format->u.raw_video.display.format != B_RGB16 &&
450		format->u.raw_video.display.format != B_RGB15 &&
451		format->u.raw_video.display.format != B_GRAY8 &&
452		format->u.raw_video.display.format != media_raw_video_format::wildcard.display.format)
453	{
454		ERROR("AcceptFormat - not a format we know about!\n");
455		return B_MEDIA_BAD_FORMAT;
456	}
457
458	if (format->u.raw_video.display.format == media_raw_video_format::wildcard.display.format)
459	{
460		format->u.raw_video.display.format = B_RGB16;
461	}
462
463	char format_string[256];
464	string_for_format(*format, format_string, 256);
465	FUNCTION("VideoConsumer::AcceptFormat: %s\n", format_string);
466
467	return B_OK;
468}
469
470//---------------------------------------------------------------
471
472status_t
473VideoConsumer::GetNextInput(
474	int32 * cookie,
475	media_input * out_input)
476{
477	FUNCTION("VideoConsumer::GetNextInput\n");
478
479	// custom build a destination for this connection
480	// put connection number in id
481
482	if (*cookie < 1)
483	{
484		mIn.node = Node();
485		mIn.destination.id = *cookie;
486		sprintf(mIn.name, "Video Consumer");
487		*out_input = mIn;
488		(*cookie)++;
489		return B_OK;
490	}
491	else
492	{
493		ERROR("VideoConsumer::GetNextInput - - BAD INDEX\n");
494		return B_MEDIA_BAD_DESTINATION;
495	}
496}
497
498//---------------------------------------------------------------
499
500void
501VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
502{
503}
504
505//---------------------------------------------------------------
506
507status_t
508VideoConsumer::GetLatencyFor(
509	const media_destination &for_whom,
510	bigtime_t * out_latency,
511	media_node_id * out_timesource)
512{
513	FUNCTION("VideoConsumer::GetLatencyFor\n");
514
515	if (for_whom != mIn.destination)
516		return B_MEDIA_BAD_DESTINATION;
517
518	*out_latency = mMyLatency;
519	*out_timesource = TimeSource()->ID();
520	return B_OK;
521}
522
523
524//---------------------------------------------------------------
525
526status_t
527VideoConsumer::FormatChanged(
528				const media_source & producer,
529				const media_destination & consumer,
530				int32 from_change_count,
531				const media_format &format)
532{
533	FUNCTION("VideoConsumer::FormatChanged\n");
534
535	if (consumer != mIn.destination)
536		return B_MEDIA_BAD_DESTINATION;
537
538	if (producer != mIn.source)
539		return B_MEDIA_BAD_SOURCE;
540
541	mIn.format = format;
542
543	return CreateBuffers(format);
544}
545
546//---------------------------------------------------------------
547
548void
549VideoConsumer::HandleEvent(
550	const media_timed_event *event,
551	bigtime_t lateness,
552	bool realTimeEvent)
553
554{
555	LOOP("VideoConsumer::HandleEvent\n");
556
557	BBuffer *buffer;
558
559	switch (event->type)
560	{
561		case BTimedEventQueue::B_START:
562			PROGRESS("VideoConsumer::HandleEvent - START\n");
563			break;
564		case BTimedEventQueue::B_STOP:
565			PROGRESS("VideoConsumer::HandleEvent - STOP\n");
566			EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
567			break;
568		case BTimedEventQueue::B_USER_EVENT:
569			PROGRESS("VideoConsumer::HandleEvent - USER EVENT\n");
570			if (RunState() == B_STARTED)
571			{
572				mTimeToFtp = true;
573				PROGRESS("Pushing user event for %.4f, time now %.4f\n", (event->event_time + mRate)/M1, event->event_time/M1);
574				media_timed_event newEvent(event->event_time + mRate, BTimedEventQueue::B_USER_EVENT);
575				EventQueue()->AddEvent(newEvent);
576			}
577				break;
578		case BTimedEventQueue::B_HANDLE_BUFFER:
579			LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n");
580			buffer = (BBuffer *) event->pointer;
581			if (RunState() == B_STARTED && mConnectionActive)
582			{
583				// see if this is one of our buffers
584				uint32 index = 0;
585				mOurBuffers = true;
586				while(index < 3)
587					if ((uint32)buffer == mBufferMap[index])
588						break;
589					else
590						index++;
591
592				if (index == 3)
593				{
594					// no, buffers belong to consumer
595					mOurBuffers = false;
596					index = 0;
597				}
598
599				if (mFtpComplete && mTimeToFtp)
600				{
601					PROGRESS("VidConsumer::HandleEvent - SPAWNING FTP THREAD\n");
602					mTimeToFtp = false;
603					mFtpComplete = false;
604					memcpy(mFtpBitmap->Bits(), buffer->Data(),mFtpBitmap->BitsLength());
605					mFtpThread = spawn_thread(FtpRun, "Video Window Ftp", B_NORMAL_PRIORITY, this);
606					resume_thread(mFtpThread);
607				}
608
609				if ( (RunMode() == B_OFFLINE) ||
610					 ((TimeSource()->Now() > (buffer->Header()->start_time - JITTER)) &&
611					  (TimeSource()->Now() < (buffer->Header()->start_time + JITTER))) )
612				{
613					if (!mOurBuffers)
614						// not our buffers, so we need to copy
615						memcpy(mBitmap[index]->Bits(), buffer->Data(),mBitmap[index]->BitsLength());
616
617					if (mWindow->Lock())
618					{
619						uint32 flags;
620						if ((mBitmap[index]->ColorSpace() == B_GRAY8) &&
621							!bitmaps_support_space(mBitmap[index]->ColorSpace(), &flags))
622						{
623							// handle mapping of GRAY8 until app server knows how
624							uint32 *start = (uint32 *)mBitmap[index]->Bits();
625							int32 size = mBitmap[index]->BitsLength();
626							uint32 *end = start + size/4;
627							for (uint32 *p = start; p < end; p++)
628								*p = (*p >> 3) & 0x1f1f1f1f;
629						}
630
631						mView->DrawBitmap(mBitmap[index]);
632						mWindow->Unlock();
633					}
634				}
635				else
636					PROGRESS("VidConsumer::HandleEvent - DROPPED FRAME\n");
637				buffer->Recycle();
638			}
639			else
640				buffer->Recycle();
641			break;
642		default:
643			ERROR("VideoConsumer::HandleEvent - BAD EVENT\n");
644			break;
645	}
646}
647
648//---------------------------------------------------------------
649
650status_t
651VideoConsumer::FtpRun(void * data)
652{
653	FUNCTION("VideoConsumer::FtpRun\n");
654
655	((VideoConsumer *)data)->FtpThread();
656
657	return 0;
658}
659
660//---------------------------------------------------------------
661
662
663void
664VideoConsumer::FtpThread()
665{
666	FUNCTION("VideoConsumer::FtpThread\n");
667
668	if (LocalSave(mFileNameText, mFtpBitmap) == B_OK)
669		FtpSave(mFileNameText);
670
671#if 0
672	// save a small version, too
673	BBitmap * b = new BBitmap(BRect(0,0,159,119), B_RGB32, true, false);
674	BView * v = new BView(BRect(0,0,159,119), "SmallView 1", 0, B_WILL_DRAW);
675	b->AddChild(v);
676
677	b->Lock();
678	v->DrawBitmap(mFtpBitmap, v->Frame());
679	v->Sync();
680	b->Unlock();
681
682	if (LocalSave("small.jpg", b) == B_OK)
683		FtpSave("small.jpg");
684
685	delete b;
686#endif
687
688	mFtpComplete = true;
689}
690
691//---------------------------------------------------------------
692
693void
694VideoConsumer::UpdateFtpStatus(char *status)
695{
696	printf("FTP STATUS: %s\n",status);
697	if (mView->Window()->Lock())
698	{
699		mStatusLine->SetText(status);
700		mView->Window()->Unlock();
701	}
702}
703
704//---------------------------------------------------------------
705
706status_t
707VideoConsumer::LocalSave(char *filename, BBitmap *bitmap)
708{
709	BFile 		*output;
710
711	UpdateFtpStatus("Capturing Image ...");
712
713	/* save a local copy of the image in the requested format */
714	output =new BFile();
715	if (output->SetTo(filename, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE) == B_NO_ERROR)
716	{
717		BBitmapStream input(bitmap);
718        status_t err = BTranslatorRoster::Default()->Translate(&input, NULL, NULL, output, mImageFormat);
719        if (err == B_OK)
720        {
721        	err = SetFileType(output, mTranslator, mImageFormat);
722        	if (err != B_OK)
723        		UpdateFtpStatus("Error setting type of output file");
724        }
725        else
726        {
727        	UpdateFtpStatus("Error writing output file");
728        }
729        input.DetachBitmap(&bitmap);
730        output->Unset();
731        delete output;
732        return B_OK;
733    }
734    else
735    {
736		UpdateFtpStatus("Error creating output file");
737    	return B_ERROR;
738    }
739}
740
741//---------------------------------------------------------------
742
743status_t
744VideoConsumer::FtpSave(char *filename)
745{
746	FtpClient 	ftp;
747
748	/* ftp the local file to our web site */
749	ftp.setPassive(mPassiveFtp);
750
751	UpdateFtpStatus("Logging in ...");
752	if (ftp.connect((string)mServerText, (string)mLoginText, (string)mPasswordText))		// connect to server
753	{
754		UpdateFtpStatus("Connected ...");
755		if (ftp.cd((string)mDirectoryText))
756		{												// cd to the desired directory
757			UpdateFtpStatus("Transmitting ...");
758			if (ftp.putFile((string)filename, (string)"temp"))			// send the file to the server
759			{
760				UpdateFtpStatus("Renaming ...");
761				if (ftp.moveFile((string)"temp",(string)filename))		// change to the desired name
762				{
763					uint32 time = real_time_clock();
764					char s[80];
765					strcpy(s,"Last Capture: ");
766					strcat(s,ctime((const long *)&time));
767					s[strlen(s)-1] = 0;
768					UpdateFtpStatus(s);
769					return B_OK;
770				}  else UpdateFtpStatus("Rename failed");
771			} else UpdateFtpStatus("File transmission failed");
772		} else UpdateFtpStatus("Couldn't find requested directory on server");
773	} else UpdateFtpStatus("Server login failed");
774	return B_ERROR;
775}
776
777//---------------------------------------------------------------
778
779status_t
780SetFileType(BFile * file,  int32 translator, uint32 type)
781{
782        translation_format * formats;
783        int32 count;
784
785        status_t err = BTranslatorRoster::Default()->GetOutputFormats(translator, (const translation_format **) &formats, &count);
786        if (err < B_OK) return err;
787        const char * mime = NULL;
788        for (int ix=0; ix<count; ix++)
789        {
790                if (formats[ix].type == type)
791                {
792                        mime = formats[ix].MIME;
793                        break;
794                }
795        }
796        if (mime == NULL)
797        {     /* this should not happen, but being defensive might be prudent */
798                return B_ERROR;
799        }
800
801        /* use BNodeInfo to set the file type */
802        BNodeInfo ninfo(file);
803        return ninfo.SetType(mime);
804}
805
806//---------------------------------------------------------------
807
808
809