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