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