2
* Copyright (C) 2007 Bryan Varner
4
* Permission is hereby granted, free of charge, to any person obtaining
5
* a copy of this software and associated documentation files (the
6
* "Software"), to deal in the Software without restriction, including
7
* without limitation the rights to use, copy, modify, merge, publish,
8
* distribute, sublicense, and/or sell copies of the Software, and to
9
* permit persons to whom the Software is furnished to do so, subject to
10
* the following conditions:
12
* The above copyright notice and this permission notice shall be included
13
* in all copies or substantial portions of the Software.
15
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
* $Id: TRWindow.cpp 4182 2007-12-17 06:30:42Z charles $
31
#include <Application.h>
35
#include <NodeMonitor.h>
36
#include <ScrollView.h>
40
#include <time.h> /* for time() */
41
#include <unistd.h> /* for sleep() */
44
#include "TRApplication.h"
45
#include "TRTransfer.h"
46
#include "TRInfoWindow.h"
48
BListView *TRWindow::transfers = NULL;
51
* The Transmission Window! Yay!
53
TRWindow::TRWindow() : BWindow(BRect(10, 40, 350, 110), "Transmission", B_TITLED_WINDOW,
54
B_ASYNCHRONOUS_CONTROLS , B_CURRENT_WORKSPACE)
59
Prefs prefs(TRANSMISSION_SETTINGS);
61
BRect *rectFrame = new BRect();
62
if (prefs.FindRect("window.frame", rectFrame) == B_OK) {
63
MoveTo(rectFrame->LeftTop());
64
ResizeTo(rectFrame->Width(), rectFrame->Height());
66
rectFrame->Set(10, 40, 350, 110);
70
BRect viewRect(0, 0, rectFrame->Width(), rectFrame->Height());
72
BMenuBar *menubar = new BMenuBar(viewRect, "MenuBar");
73
BMenu *menu = new BMenu("File");
74
menu->AddItem(new BMenuItem("Open", new BMessage(TR_OPEN), 'O', B_COMMAND_KEY));
75
menu->FindItem(TR_OPEN)->SetTarget(be_app_messenger); // send OPEN to the be_app.
76
menu->AddSeparatorItem();
77
menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q', B_COMMAND_KEY));
78
menubar->AddItem(menu);
80
menu = new BMenu("Torrent");
81
menu->AddItem(new BMenuItem("Get Info", new BMessage(TR_INFO), 'I', B_COMMAND_KEY));
82
menu->FindItem(TR_INFO)->SetEnabled(false);
83
menu->AddSeparatorItem();
84
menu->AddItem(new BMenuItem("Resume", new BMessage(TR_RESUME)));
85
menu->AddItem(new BMenuItem("Pause", new BMessage(TR_PAUSE)));
86
menu->AddItem(new BMenuItem("Remove", new BMessage(TR_REMOVE)));
87
menubar->AddItem(menu);
89
menu = new BMenu("Tools");
90
menu->AddItem(new BMenuItem("Settings", new BMessage(TR_SETTINGS)));
91
menu->FindItem(TR_SETTINGS)->SetTarget(be_app_messenger);
92
menu->AddSeparatorItem();
93
menu->AddItem(new BMenuItem("About Transmission", new BMessage(B_ABOUT_REQUESTED)));
94
menu->FindItem(B_ABOUT_REQUESTED)->SetTarget(be_app_messenger);
95
menubar->AddItem(menu);
98
SetKeyMenuBar(menubar);
100
// TODO: Tool Bar? (Well after everything is working based on Menus)
102
// Setup the transfers ListView
103
viewRect.Set(2, menubar->Frame().bottom + 3, rectFrame->Width() - 2 - B_V_SCROLL_BAR_WIDTH, rectFrame->Height() - 2);
104
TRWindow::transfers = new BListView(viewRect, "TorrentList", B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL);
105
TRWindow::transfers->SetSelectionMessage(new BMessage(TR_SELECT));
106
AddChild(new BScrollView("TransferScroller", transfers, B_FOLLOW_ALL, 0, false, true));
111
// Bring up the Transmission Engine
112
engine = tr_init( "beos" );
115
UpdateList(-1, true);
117
// Start the message loop without showing the window.
122
static void torrentclose(tr_torrent_t *torrent, void *)
124
tr_torrentClose(torrent);
127
TRWindow::~TRWindow() {
128
tr_torrentIterate(engine, torrentclose, NULL);
129
const int MAX_EXIT_WAIT_SECS = 10;
130
const time_t deadline = time(0) + MAX_EXIT_WAIT_SECS;
131
while (tr_torrentCount(engine) && time(NULL) < deadline) {
134
/* XXX there's no way to make sure the torrent threads are running so this might crash */
141
void TRWindow::LoadSettings() {
142
if (engine != NULL) {
143
Prefs prefs(TRANSMISSION_SETTINGS);
146
if (prefs.FindInt32("transmission.bindPort", &bindPort) != B_OK) {
148
prefs.SetInt32("transmission.bindPort", bindPort);
150
tr_setBindPort(engine, (int)bindPort);
153
if (prefs.FindInt32("transmission.uploadLimit", &uploadLimit) != B_OK) {
155
prefs.SetInt32("transmission.uploadLimit", uploadLimit);
157
tr_setGlobalSpeedLimit(engine, TR_UP, (int)uploadLimit);
163
* Rescans the active Torrents folder, and will add all the torrents there to the
164
* engine. Called during initial Application Start & Stop.
166
void TRWindow::RescanTorrents() {
168
TRApplication *app = dynamic_cast<TRApplication*>(be_app);
169
BEntry *torrentEntry = new BEntry();
172
if (app->TorrentDir()->InitCheck() == B_OK) {
173
err = app->TorrentDir()->Rewind();
174
while (err == B_OK) {
175
err = app->TorrentDir()->GetNextEntry(torrentEntry, true);
176
if (err != B_ENTRY_NOT_FOUND) {
177
AddEntry(torrentEntry);
188
* Adds the file specified by *torrent to the Transmission engine.
189
* Then adds a new TRTransfer item in the transfers list.
190
* This item holds cached information about the torrent entry and node.
191
* These TRTransmission items are _NOT_ guaranteed to render the entry
192
* they were created from.
194
void TRWindow::AddEntry(BEntry *torrent) {
196
if (torrent->GetNodeRef(&node) == B_OK) {
197
if (watch_node(&node, B_WATCH_NAME, this) == B_OK) {
199
torrent->GetPath(&path);
201
// Try adding the torrent to the engine.
203
tr_torrent_t *nTorrent;
204
nTorrent = tr_torrentInit(engine, path.Path(), GetFolder().String(),
205
TR_FLAG_PAUSED, &error);
206
if (nTorrent != NULL && Lock()) { // Success. Add the TRTorrent item.
207
transfers->AddItem(new TRTransfer(path.Path(), node, nTorrent));
209
bool autoStart = true;
211
// Decide if we should auto-start this torrent or not.
212
BString prefName("download.");
213
prefName << path.Path() << ".running";
215
Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
216
if (prefs->FindBool(prefName.String(), &autoStart) != B_OK) {
222
// Start the newly added torrent.
223
worker_info *startData = (worker_info*)calloc(1, sizeof(worker_info));
224
startData->window = this;
225
startData->torrent = nTorrent;
226
thread_id start_thread = spawn_thread(TRWindow::AsynchStartTorrent, "BirthCanal",
227
B_NORMAL_PRIORITY, (void *)startData);
228
if (!((start_thread) < B_OK)) {
229
resume_thread(start_thread);
230
} else { // Fallback and start the old way.
231
StartTorrent(startData->torrent);
237
bool duplicate = false;
239
for (int32 i = 0; i < transfers->CountItems(); i++) {
240
tr = (TRTransfer*)transfers->ItemAt(i);
241
if (tr->GetCachedNodeRef() == node) {
246
BString errmsg("An error occurred trying to read ");
247
char namebuf[B_FILE_NAME_LENGTH];
248
torrent->GetName(namebuf);
252
BAlert *error = new BAlert("Error Opening Torrent",
255
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
265
void TRWindow::MessageReceived(BMessage *msg) {
267
* The only messages we receive from the node_monitor are if we need to
268
* stop watching the node. Basically, if it's been moved or removed we stop.
270
if (msg->what == B_NODE_MONITOR) {
276
if ((msg->FindInt32("opcode", &opcode) == B_OK) &&
277
(msg->FindInt64("node", &node.node) == B_OK) &&
278
(msg->FindInt32("device", &node.device) == B_OK))
280
bool stop = (opcode == B_ENTRY_REMOVED);
283
msg->FindInt64("directory", &toDir);
284
} else { // It must have moved.
285
stop = ((msg->FindInt64("from directory", &fromDir) == B_OK) &&
286
(msg->FindInt64("to directory", &toDir) == B_OK) &&
291
watch_node(&node, B_STOP_WATCHING, this);
293
/* Find the full path from the TRTorrents.
294
* The index of the TRTorrent that is caching the information
295
* IS NOT the index of the torrent in the engine. These are
296
* Totally decoupled, due to the way transmission is written.
298
remove_info *removeData = (remove_info*)calloc(1, sizeof(remove_info));
299
removeData->window = this;
301
for (int32 i = 0; i < transfers->CountItems(); i++) {
302
item = (TRTransfer*)transfers->ItemAt(i);
303
if (item->GetCachedNodeRef() == node) {
304
strcpy(removeData->path, item->GetCachedPath());
308
transfers->DoForEach(TRWindow::RemovePath, (void *)removeData);
313
} else if (msg->what == TR_INFO) {
314
// Display an Info Window.
315
TRTransfer *transfer = dynamic_cast<TRTransfer*>(transfers->ItemAt(transfers->CurrentSelection()));
316
const tr_stat_t *s = tr_torrentStat(transfer->GetTorrent());
317
const tr_info_t *i = tr_torrentInfo(transfer->GetTorrent());
319
TRInfoWindow *info = new TRInfoWindow(s, i, tr_torrentGetFolder(transfer->GetTorrent()));
320
info->MoveTo(Frame().LeftTop() + BPoint(20, 25));
322
} else if (msg->what == TR_SELECT) {
323
// Setup the Torrent Menu enabled / disabled state.
325
msg->FindInt32("index", &selection);
326
UpdateList(selection, true);
327
} else if (msg->what == TR_RESUME) {
328
worker_info *startData = (worker_info*)calloc(1, sizeof(worker_info));
329
startData->window = this;
330
startData->torrent = (dynamic_cast<TRTransfer*>(transfers->ItemAt(transfers->CurrentSelection())))->GetTorrent();
331
thread_id start_thread = spawn_thread(TRWindow::AsynchStartTorrent, "BirthCanal",
332
B_NORMAL_PRIORITY, (void *)startData);
333
if (!((start_thread) < B_OK)) {
334
resume_thread(start_thread);
335
} else { // Fallback and start the old way.
336
StartTorrent(startData->torrent);
339
} else if (msg->what == TR_PAUSE) {
340
worker_info *stopData = (worker_info*)calloc(1, sizeof(worker_info));
341
stopData->window = this;
342
stopData->torrent = (dynamic_cast<TRTransfer*>(transfers->ItemAt(transfers->CurrentSelection())))->GetTorrent();
343
thread_id stop_thread = spawn_thread(TRWindow::AsynchStopTorrent, "InUtero",
344
B_NORMAL_PRIORITY, (void *)stopData);
345
if (!((stop_thread) < B_OK)) {
346
resume_thread(stop_thread);
347
} else { // Fallback and stop it the old way.
348
StopTorrent(stopData->torrent);
351
} else if (msg->what == TR_REMOVE) {
352
int32 index = transfers->CurrentSelection();
354
// Remove the file from the filesystem.
355
TRTransfer *item = (TRTransfer*)transfers->RemoveItem(index);
356
tr_torrentClose(item->GetTorrent());
357
BEntry *entry = new BEntry(item->GetCachedPath(), true);
364
UpdateList(transfers->CurrentSelection(), true);
365
} else if (msg->what == B_SIMPLE_DATA) {
366
be_app->RefsReceived(msg);
369
BWindow::MessageReceived(msg);
373
* Handles QuitRequests.
374
* Displays a BAlert asking if the user really wants to quit if torrents are running.
375
* If affimative, then we'll stop all the running torrents.
377
bool TRWindow::QuitRequested() {
384
quit_info *quitData = (quit_info*)calloc(1, sizeof(quit_info));
385
quitData->running = 0;
386
transfers->DoForEach(TRWindow::CheckQuitStatus, (void *)quitData);
387
running = quitData->running;
392
quitMsg << "There's " << running << " torrent";
396
quitMsg << " currently running.\n"
397
<< "What would you like to do?";
399
BAlert *confirmQuit = new BAlert("Confirm Quit", quitMsg.String(),
400
"Cancel", "Quit", NULL,
401
B_WIDTH_AS_USUAL, B_WARNING_ALERT);
402
quit = (confirmQuit->Go() == 1);
408
Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
409
prefs->SetRect("window.frame", Frame());
412
for (int i = 0; i < transfers->CountItems(); i++) {
413
strItem = "download.";
414
tr_torrent_t *torrent = (dynamic_cast<TRTransfer*>(transfers->ItemAt(i)))->GetTorrent();
415
const tr_info_t *info = tr_torrentInfo(torrent);
416
const tr_stat_t *stat = tr_torrentStat(torrent);
418
strItem << info->torrent << ".running";
419
if (stat->status & (TR_STATUS_CHECK_WAIT | TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED)) {
420
prefs->SetBool(strItem.String(), true);
421
tr_torrentStop(torrent);
423
prefs->SetBool(strItem.String(), false);
430
BAlert *waiting = new BAlert("Stopping Torrents", "Waiting for torrents to stop...", "Quit");
431
quitter = new BInvoker(new BMessage(B_QUIT_REQUESTED), BMessenger(be_app));
432
waiting->Go(quitter);
435
be_app->PostMessage(new BMessage(B_QUIT_REQUESTED));
441
void TRWindow::FrameResized(float, float) {
442
transfers->Invalidate();
447
* Called from the StopTorrent thread.
449
void TRWindow::StopTorrent(tr_torrent_t *torrent) {
450
tr_torrentStop(torrent);
452
UpdateList(transfers->CurrentSelection(), true);
455
BString TRWindow::GetFolder(void) {
456
// Read the settings.
458
Prefs *prefs = new Prefs(TRANSMISSION_SETTINGS);
459
if (prefs->FindString("download.folder", &folder) != B_OK) {
460
prefs->SetString("download.folder", "/boot/home/Downloads");
461
folder << "/boot/home/Downloads";
468
* Called from StartTorrent thread.
470
void TRWindow::StartTorrent(tr_torrent_t *torrent) {
471
tr_torrentSetFolder(torrent, GetFolder().String());
472
tr_torrentStart(torrent);
474
if (transfers->CurrentSelection() >= 0) {
475
UpdateList(transfers->CurrentSelection(), true);
480
* Called from the be_app Pulse();
481
* This will update the data structures that the TRTorrents use to render,
482
* and invalidate the view.
484
void TRWindow::UpdateList(int32 selection = -1, bool menus = true) {
487
quit_info *quitData = (quit_info*)calloc(1, sizeof(quit_info));
488
quitData->running = 0;
489
transfers->DoForEach(TRWindow::CheckQuitStatus, (void *)quitData);
490
if (quitData->running == 0)
491
be_app->PostMessage(new BMessage(B_QUIT_REQUESTED));
495
update_info *upData = (update_info*)calloc(1, sizeof(update_info));
496
upData->running = false;
497
upData->selected = selection;
500
transfers->DoForEach(TRWindow::UpdateStats, (void *)upData);
503
KeyMenuBar()->FindItem(TR_INFO)->SetEnabled(selection >= 0);
504
KeyMenuBar()->FindItem(TR_RESUME)->SetEnabled(selection >= 0 && !upData->running);
505
KeyMenuBar()->FindItem(TR_PAUSE)->SetEnabled(selection >= 0 && upData->running);
506
KeyMenuBar()->FindItem(TR_REMOVE)->SetEnabled(selection >= 0 && !upData->running);
509
if (upData->invalid > 0 && transfers->LockLooper()) {
510
transfers->Invalidate();
511
transfers->UnlockLooper();
520
* Thread Function to stop Torrents. This can be expensive and causes the event loop to
523
int32 TRWindow::AsynchStopTorrent(void *data) {
524
worker_info* stopData = (worker_info*)data;
525
stopData->window->StopTorrent(stopData->torrent);
531
* Thread Function to start Torrents. This can be expensive and causes the event loop to
534
int32 TRWindow::AsynchStartTorrent(void *data) {
535
worker_info* startData = (worker_info*)data;
536
startData->window->StartTorrent(startData->torrent);
542
* Invoked by DoForEach upon the transfers list. This will
543
* remove the item that is caching the path specified by
546
bool TRWindow::RemovePath(BListItem *item, void *data) {
547
remove_info* removeData = (remove_info*)data;
548
TRTransfer *transfer = dynamic_cast<TRTransfer*>(item);
550
if (strcmp(transfer->GetCachedPath(), removeData->path) == 0) {
551
removeData->window->transfers->RemoveItem(transfer);
558
* Invoked during QuitRequested, iterates all Transfers and
559
* checks to see how many are running.
561
bool TRWindow::CheckQuitStatus(BListItem *item, void *data) {
562
quit_info* quitData = (quit_info*)data;
563
TRTransfer *transfer = dynamic_cast<TRTransfer*>(item);
565
if (transfer->IsRunning()) {
572
* Invoked during UpdateList()
574
bool TRWindow::UpdateStats(BListItem *item, void *data) {
575
update_info* upData = (update_info*)data;
576
TRTransfer *transfer = dynamic_cast<TRTransfer*>(item);
578
int32 index = transfers->IndexOf(transfer);
579
if (transfer->UpdateStatus(tr_torrentStat(transfer->GetTorrent()), index % 2 == 0)) {
583
if (index == upData->selected) {
584
upData->running = transfer->IsRunning();