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
46static status_t SetFileType(BFile* file, int32 translator, uint32 type);
47
48const 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
52VideoConsumer::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
97VideoConsumer::~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
131BMediaAddOn*
132VideoConsumer::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
144void
145VideoConsumer::Start(bigtime_t performanceTime)
146{
147	BMediaEventLooper::Start(performanceTime);
148}
149
150
151void
152VideoConsumer::Stop(bigtime_t performanceTime, bool immediate)
153{
154	BMediaEventLooper::Stop(performanceTime, immediate);
155}
156
157
158void
159VideoConsumer::Seek(bigtime_t mediaTime, bigtime_t performanceTime)
160{
161	BMediaEventLooper::Seek(mediaTime, performanceTime);
162}
163
164
165void
166VideoConsumer::TimeWarp(bigtime_t atRealTime, bigtime_t toPerformanceTime)
167{
168	BMediaEventLooper::TimeWarp(atRealTime, toPerformanceTime);
169}
170
171
172status_t
173VideoConsumer::DeleteHook(BMediaNode* node)
174{
175	return BMediaEventLooper::DeleteHook(node);
176}
177
178
179void
180VideoConsumer::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
193status_t
194VideoConsumer::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
213status_t
214VideoConsumer::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
250void
251VideoConsumer::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
268void
269VideoConsumer::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
279status_t
280VideoConsumer::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
355void
356VideoConsumer::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
375status_t
376VideoConsumer::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
405void
406VideoConsumer::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
421status_t
422VideoConsumer::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
463status_t
464VideoConsumer::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
486void
487VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
488{
489}
490
491
492status_t
493VideoConsumer::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
507status_t
508VideoConsumer::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
526void
527VideoConsumer::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
634status_t
635VideoConsumer::FtpRun(void* data)
636{
637	FUNCTION("VideoConsumer::FtpRun\n");
638
639	((VideoConsumer*)data)->FtpThread();
640
641	return 0;
642}
643
644
645void
646VideoConsumer::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
679void
680VideoConsumer::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
690status_t
691VideoConsumer::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
725status_t
726VideoConsumer::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
793status_t
794SetFileType(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