Display sources in feed list; right-click menus
This commit is contained in:
parent
726d6346a1
commit
c712468177
1
Makefile
1
Makefile
|
@ -35,6 +35,7 @@ SRCS = \
|
||||||
src/Feed.cpp \
|
src/Feed.cpp \
|
||||||
src/FeedEditWindow.cpp \
|
src/FeedEditWindow.cpp \
|
||||||
src/FeedListItem.cpp \
|
src/FeedListItem.cpp \
|
||||||
|
src/FeedListView.cpp \
|
||||||
src/FeedsView.cpp \
|
src/FeedsView.cpp \
|
||||||
src/MainWindow.cpp \
|
src/MainWindow.cpp \
|
||||||
src/Mimetypes.cpp \
|
src/Mimetypes.cpp \
|
||||||
|
|
|
@ -59,13 +59,19 @@ FeedController::MessageReceived(BMessage* msg)
|
||||||
case kEnqueueFeed:
|
case kEnqueueFeed:
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
const void* data;
|
BString feedID;
|
||||||
|
BString feedSource;
|
||||||
ssize_t size = sizeof(Feed);
|
ssize_t size = sizeof(Feed);
|
||||||
|
|
||||||
while (msg->HasData("feeds", B_RAW_TYPE, i)) {
|
while (msg->HasString("feed_identifiers", i)) {
|
||||||
msg->FindData("feeds", B_RAW_TYPE, i, &data, &size);
|
msg->FindString("feed_identifiers", i, &feedID);
|
||||||
fDownloadQueue->AddItem((Feed*)data);
|
msg->FindString("feed_sources", i, &feedSource);
|
||||||
|
|
||||||
|
Feed* feed = SourceManager::GetFeed(feedID.String(),
|
||||||
|
feedSource.String());
|
||||||
|
fDownloadQueue->AddItem(feed);
|
||||||
_SendProgress();
|
_SendProgress();
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
fMessageRunner->SetCount(-1);
|
fMessageRunner->SetCount(-1);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -152,10 +152,11 @@ FeedEditWindow::_SaveFeed()
|
||||||
SourceManager::EditFeed(fFeed);
|
SourceManager::EditFeed(fFeed);
|
||||||
|
|
||||||
BMessage edited(kFeedsEdited);
|
BMessage edited(kFeedsEdited);
|
||||||
// BMessage enqueueUpdated(kEnqueueFeed);
|
BMessage enqueueUpdated(kEnqueueFeed);
|
||||||
// enqueueUpdated.AddData("feeds", B_RAW_TYPE, &fFeed, sizeof(Feed));
|
enqueueUpdated.AddString("feed_identifiers", fFeed->Identifier());
|
||||||
//
|
enqueueUpdated.AddString("feed_sources", fFeed->Source());
|
||||||
// ((App*)be_app)->MessageReceived(&enqueueUpdated);
|
|
||||||
|
((App*)be_app)->MessageReceived(&enqueueUpdated);
|
||||||
((App*)be_app)->PostMessage(&edited);
|
((App*)be_app)->PostMessage(&edited);
|
||||||
Quit();
|
Quit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ FeedListItem::FeedListItem(Feed* feed)
|
||||||
fFeedIdentifier(feed->Identifier()),
|
fFeedIdentifier(feed->Identifier()),
|
||||||
fFeedSource(feed->Source())
|
fFeedSource(feed->Source())
|
||||||
{
|
{
|
||||||
|
fFeed = feed;
|
||||||
if (feed->Title().IsEmpty() == true)
|
if (feed->Title().IsEmpty() == true)
|
||||||
SetText(B_TRANSLATE("Untitled Feed"));
|
SetText(B_TRANSLATE("Untitled Feed"));
|
||||||
}
|
}
|
||||||
|
@ -87,3 +88,10 @@ FeedListItem::SetStatus(int8 status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Feed*
|
||||||
|
FeedListItem::GetFeed()
|
||||||
|
{
|
||||||
|
return fFeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ public:
|
||||||
BUrl FeedUrl();
|
BUrl FeedUrl();
|
||||||
const char* FeedIdentifier();
|
const char* FeedIdentifier();
|
||||||
const char* FeedSource();
|
const char* FeedSource();
|
||||||
|
Feed* GetFeed();
|
||||||
|
|
||||||
void SetStatus(int8 status);
|
void SetStatus(int8 status);
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ private:
|
||||||
BUrl fFeedUrl;
|
BUrl fFeedUrl;
|
||||||
BString fFeedIdentifier;
|
BString fFeedIdentifier;
|
||||||
BString fFeedSource;
|
BString fFeedSource;
|
||||||
|
Feed* fFeed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FeedListView.h"
|
||||||
|
|
||||||
|
#include <Catalog.h>
|
||||||
|
#include <MenuItem.h>
|
||||||
|
#include <OutlineListView.h>
|
||||||
|
#include <PopUpMenu.h>
|
||||||
|
#include <Window.h>
|
||||||
|
|
||||||
|
#include "App.h"
|
||||||
|
|
||||||
|
|
||||||
|
#undef B_TRANSLATION_CONTEXT
|
||||||
|
#define B_TRANSLATION_CONTEXT "FeedListView"
|
||||||
|
|
||||||
|
|
||||||
|
FeedListView::FeedListView(const char* name)
|
||||||
|
: BOutlineListView(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
FeedListView::MouseDown(BPoint where)
|
||||||
|
{
|
||||||
|
BOutlineListView::MouseDown(where);
|
||||||
|
|
||||||
|
uint32 buttons = 0;
|
||||||
|
Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
|
||||||
|
|
||||||
|
if (!(buttons & B_SECONDARY_MOUSE_BUTTON))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BMenuItem* item;
|
||||||
|
int32 selIndex = CurrentSelection();
|
||||||
|
if (Superitem(ItemAt(selIndex)) == NULL)
|
||||||
|
item = _SourcePopUp()->Go(ConvertToScreen(where));
|
||||||
|
else
|
||||||
|
item = _FeedPopUp()->Go(ConvertToScreen(where));
|
||||||
|
|
||||||
|
if (item != NULL)
|
||||||
|
Window()->MessageReceived(item->Message());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BPopUpMenu*
|
||||||
|
FeedListView::_FeedPopUp()
|
||||||
|
{
|
||||||
|
BPopUpMenu* menu = new BPopUpMenu("feedPopUp");
|
||||||
|
menu->SetTargetForItems(Window());
|
||||||
|
menu->AddItem(new BMenuItem(B_TRANSLATE("Update feed"),
|
||||||
|
new BMessage(kFeedsEnqueueSelected)));
|
||||||
|
menu->AddItem(new BMenuItem(B_TRANSLATE("Edit feed…"),
|
||||||
|
new BMessage(kFeedsEditSelected)));
|
||||||
|
menu->AddItem(new BMenuItem(B_TRANSLATE("Remove feed…"),
|
||||||
|
new BMessage(kFeedsRemoveSelected)));
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BPopUpMenu*
|
||||||
|
FeedListView::_SourcePopUp()
|
||||||
|
{
|
||||||
|
BMessage enqueue(kFeedsEnqueueSelected);
|
||||||
|
|
||||||
|
BPopUpMenu* menu = new BPopUpMenu("sourcePopUp");
|
||||||
|
menu->AddItem(new BMenuItem(B_TRANSLATE("Update source"),
|
||||||
|
new BMessage(kFeedsEnqueueSelected)));
|
||||||
|
// menu->AddItem(BMenuItem("Configure source…", BMessage('')));
|
||||||
|
// menu->AddItem(BMenuItem("Remove source…", BMessage('')));
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT license.
|
||||||
|
*/
|
||||||
|
#ifndef FEEDLISTVIEW_H
|
||||||
|
#define FEEDLISTVIEW_H
|
||||||
|
|
||||||
|
#include <OutlineListView.h>
|
||||||
|
#include <Point.h>
|
||||||
|
|
||||||
|
class BPopUpMenu;
|
||||||
|
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
kFeedsRemoveSelected = 'srem',
|
||||||
|
kFeedsEditSelected = 'sedt',
|
||||||
|
kFeedsEnqueueSelected = 'senq'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class FeedListView : public BOutlineListView {
|
||||||
|
public:
|
||||||
|
FeedListView(const char* name);
|
||||||
|
|
||||||
|
void MouseDown(BPoint where);
|
||||||
|
|
||||||
|
private:
|
||||||
|
BPopUpMenu* _FeedPopUp();
|
||||||
|
BPopUpMenu* _SourcePopUp();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // FEEDLISTVIEW_H
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
|
||||||
* All rights reserved. Distributed under the terms of the MIT license.
|
|
||||||
*/
|
|
||||||
#ifndef FEEDSOURCE_H
|
|
||||||
#define FEEDSOURCE_H
|
|
||||||
|
|
||||||
#include <StringList.h>
|
|
||||||
|
|
||||||
#include "Feed.h"
|
|
||||||
|
|
||||||
|
|
||||||
// Hierarchy: FeedSource → Feed → Entry
|
|
||||||
// TODO: RemoveFeed(), AddFeed(), etc.
|
|
||||||
class FeedSource {
|
|
||||||
public:
|
|
||||||
static BStringList FeedIdentifiers();
|
|
||||||
|
|
||||||
Feed RemoveFeed(Feed feed);
|
|
||||||
Feed RemoveFeed(const char* identifier);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // FEEDSOURCE_H
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "FeedController.h"
|
#include "FeedController.h"
|
||||||
#include "FeedEditWindow.h"
|
#include "FeedEditWindow.h"
|
||||||
#include "FeedListItem.h"
|
#include "FeedListItem.h"
|
||||||
|
#include "FeedListView.h"
|
||||||
#include "Notifier.h"
|
#include "Notifier.h"
|
||||||
#include "SourceListItem.h"
|
#include "SourceListItem.h"
|
||||||
#include "SourceManager.h"
|
#include "SourceManager.h"
|
||||||
|
@ -43,25 +44,30 @@ FeedsView::MessageReceived(BMessage* msg)
|
||||||
{
|
{
|
||||||
switch (msg->what)
|
switch (msg->what)
|
||||||
{
|
{
|
||||||
case kFeedsAddButton:
|
case kFeedsEnqueueSelected:
|
||||||
|
{
|
||||||
|
_UpdateSelected();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kFeedsRemoveSelected:
|
||||||
|
{
|
||||||
|
if (_RemoveSelected() == true)
|
||||||
|
_PopulateFeedList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kFeedsEditSelected:
|
||||||
|
{
|
||||||
|
if (fFeedsListView->CurrentSelection() >= 0)
|
||||||
|
_EditSelected();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kFeedsAddNew:
|
||||||
{
|
{
|
||||||
FeedEditWindow* edit = new FeedEditWindow();
|
FeedEditWindow* edit = new FeedEditWindow();
|
||||||
edit->Show();
|
edit->Show();
|
||||||
edit->Activate();
|
edit->Activate();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case kFeedsRemoveButton:
|
|
||||||
{
|
|
||||||
_RemoveSelectedFeed();
|
|
||||||
_PopulateFeedList();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case kFeedsEditButton:
|
|
||||||
{
|
|
||||||
if (fFeedsListView->CurrentSelection() >= 0)
|
|
||||||
_EditSelectedFeed();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case kFeedsSelected:
|
case kFeedsSelected:
|
||||||
{
|
{
|
||||||
bool enabled = msg->GetInt32("index", -1) >= 0;
|
bool enabled = msg->GetInt32("index", -1) >= 0;
|
||||||
|
@ -116,23 +122,24 @@ void
|
||||||
FeedsView::_InitInterface()
|
FeedsView::_InitInterface()
|
||||||
{
|
{
|
||||||
// Feeds list
|
// Feeds list
|
||||||
fFeedsListView = new BOutlineListView("feedsList");
|
fFeedsListView = new FeedListView("feedsList");
|
||||||
fFeedsScrollView = new BScrollView("feedsScroll", fFeedsListView,
|
fFeedsScrollView = new BScrollView("feedsScroll", fFeedsListView,
|
||||||
B_WILL_DRAW, false, true);
|
B_WILL_DRAW, false, true);
|
||||||
fFeedsListView->SetSelectionMessage(new BMessage(kFeedsSelected));
|
fFeedsListView->SetSelectionMessage(new BMessage(kFeedsSelected));
|
||||||
fFeedsListView->SetInvocationMessage(new BMessage(kFeedsEditButton));
|
fFeedsListView->SetInvocationMessage(new BMessage(kFeedsEditSelected));
|
||||||
|
|
||||||
_PopulateFeedList();
|
_PopulateFeedList();
|
||||||
|
|
||||||
fProgressLabel = new BStringView("progressLabel", "");
|
fProgressLabel = new BStringView("progressLabel", "");
|
||||||
|
|
||||||
// Add, Remove, Edit
|
// Add, Remove, Edit
|
||||||
fAddButton = new BButton("addFeed", "+", new BMessage(kFeedsAddButton));
|
fAddButton = new BButton("addFeed", "+", new BMessage(kFeedsAddNew));
|
||||||
fRemoveButton = new BButton("removeFeed", "-", new BMessage(kFeedsRemoveButton));
|
fRemoveButton = new BButton("removeFeed", "-",
|
||||||
|
new BMessage(kFeedsRemoveSelected));
|
||||||
fAddButton->SetToolTip(B_TRANSLATE("Add new feed"));
|
fAddButton->SetToolTip(B_TRANSLATE("Add new feed"));
|
||||||
fRemoveButton->SetToolTip(B_TRANSLATE("Remove selected feed"));
|
fRemoveButton->SetToolTip(B_TRANSLATE("Remove selected feed"));
|
||||||
fEditButton = new BButton("editFeed", B_TRANSLATE("Edit…"),
|
fEditButton = new BButton("editFeed", B_TRANSLATE("Edit…"),
|
||||||
new BMessage(kFeedsEditButton));
|
new BMessage(kFeedsEditSelected));
|
||||||
|
|
||||||
font_height fontHeight;
|
font_height fontHeight;
|
||||||
GetFontHeight(&fontHeight);
|
GetFontHeight(&fontHeight);
|
||||||
|
@ -184,22 +191,62 @@ FeedsView::_InitInterface()
|
||||||
.End();
|
.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
FeedsView::_EditSelectedFeed()
|
FeedsView::_UpdateSelected()
|
||||||
{
|
{
|
||||||
int32 selIndex = fFeedsListView->CurrentSelection();
|
int32 selIndex = fFeedsListView->CurrentSelection();
|
||||||
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
|
|
||||||
FeedEditWindow* edit = new FeedEditWindow(selected->FeedIdentifier(),
|
|
||||||
selected->FeedSource());
|
|
||||||
|
|
||||||
edit->Show();
|
// Enqueue single feed
|
||||||
edit->Activate();
|
if (fFeedsListView->Superitem(fFeedsListView->ItemAt(selIndex)) != NULL) {
|
||||||
|
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
|
||||||
|
|
||||||
|
BMessage enqueue(kEnqueueFeed);
|
||||||
|
enqueue.AddString("feed_identifiers", selected->FeedIdentifier());
|
||||||
|
enqueue.AddString("feed_sources", selected->FeedSource());
|
||||||
|
|
||||||
|
((App*)be_app)->MessageReceived(&enqueue);
|
||||||
|
}
|
||||||
|
// Enqueue feeds from a source
|
||||||
|
else {
|
||||||
|
BMessage enqueue(kEnqueueFeed);
|
||||||
|
SourceListItem* selected = (SourceListItem*)fFeedsListView->ItemAt(selIndex);
|
||||||
|
BObjectList<Feed> feeds = selected->FeedSource()->Feeds();
|
||||||
|
|
||||||
|
for (int i = 0; i < feeds.CountItems(); i++) {
|
||||||
|
Feed* selected = feeds.ItemAt(i);
|
||||||
|
enqueue.AddString("feed_identifiers", selected->Identifier());
|
||||||
|
enqueue.AddString("feed_sources", selected->Source());
|
||||||
|
}
|
||||||
|
((App*)be_app)->MessageReceived(&enqueue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
FeedsView::_RemoveSelectedFeed()
|
FeedsView::_EditSelected()
|
||||||
|
{
|
||||||
|
int32 selIndex = fFeedsListView->CurrentSelection();
|
||||||
|
|
||||||
|
if (fFeedsListView->Superitem(fFeedsListView->ItemAt(selIndex)) != NULL) {
|
||||||
|
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
|
||||||
|
|
||||||
|
FeedEditWindow* edit = new FeedEditWindow(selected->FeedIdentifier(),
|
||||||
|
selected->FeedSource());
|
||||||
|
|
||||||
|
edit->Show();
|
||||||
|
edit->Activate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SourceListItem* selected = (SourceListItem*)fFeedsListView->ItemAt(selIndex);
|
||||||
|
Source* source = selected->FeedSource();
|
||||||
|
// if (source->IsConfigurable() == true)
|
||||||
|
// … Do something about that
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
FeedsView::_RemoveSelected()
|
||||||
{
|
{
|
||||||
BAlert* alert = new BAlert(B_TRANSLATE("Confirm removal"),
|
BAlert* alert = new BAlert(B_TRANSLATE("Confirm removal"),
|
||||||
B_TRANSLATE("Are you sure you want to remove the selected feed?"),
|
B_TRANSLATE("Are you sure you want to remove the selected feed?"),
|
||||||
|
@ -209,13 +256,17 @@ FeedsView::_RemoveSelectedFeed()
|
||||||
alert->SetShortcut(1, B_ESCAPE);
|
alert->SetShortcut(1, B_ESCAPE);
|
||||||
int32 button = alert->Go();
|
int32 button = alert->Go();
|
||||||
if (button != 0)
|
if (button != 0)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
int32 selIndex = fFeedsListView->CurrentSelection();
|
int32 selIndex = fFeedsListView->CurrentSelection();
|
||||||
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
|
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
|
||||||
|
|
||||||
|
if (fFeedsListView->Superitem(selected) == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
SourceManager::RemoveFeed(SourceManager::GetFeed(selected->FeedIdentifier(),
|
SourceManager::RemoveFeed(SourceManager::GetFeed(selected->FeedIdentifier(),
|
||||||
selected->FeedSource()));
|
selected->FeedSource()));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,18 +10,16 @@
|
||||||
#include <GroupView.h>
|
#include <GroupView.h>
|
||||||
|
|
||||||
class BMessage;
|
class BMessage;
|
||||||
class BOutlineListView;
|
class FeedListView;
|
||||||
class BScrollView;
|
class BScrollView;
|
||||||
class BStringView;
|
class BStringView;
|
||||||
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
kFeedsAddButton = 'sadd',
|
kFeedsAddNew = 'sadd',
|
||||||
kFeedsRemoveButton = 'srem',
|
kFeedsSelected = 'flsl',
|
||||||
kFeedsEditButton = 'sedt',
|
kFeedsEdited = 'fedd'
|
||||||
kFeedsSelected = 'flsl',
|
|
||||||
kFeedsEdited = 'fedd'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,8 +32,9 @@ public:
|
||||||
private:
|
private:
|
||||||
void _InitInterface();
|
void _InitInterface();
|
||||||
|
|
||||||
void _EditSelectedFeed();
|
void _UpdateSelected();
|
||||||
void _RemoveSelectedFeed();
|
void _EditSelected();
|
||||||
|
bool _RemoveSelected();
|
||||||
|
|
||||||
void _PopulateFeedList();
|
void _PopulateFeedList();
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ private:
|
||||||
BButton* fRemoveButton;
|
BButton* fRemoveButton;
|
||||||
BButton* fEditButton;
|
BButton* fEditButton;
|
||||||
BStringView* fProgressLabel;
|
BStringView* fProgressLabel;
|
||||||
BOutlineListView* fFeedsListView;
|
FeedListView* fFeedsListView;
|
||||||
BScrollView* fFeedsScrollView;
|
BScrollView* fFeedsScrollView;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,436 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
|
||||||
* All rights reserved. Distributed under the terms of the MIT license.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "LocalSource.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <Catalog.h>
|
|
||||||
#include <Directory.h>
|
|
||||||
#include <Entry.h>
|
|
||||||
#include <File.h>
|
|
||||||
#include <FindDirectory.h>
|
|
||||||
#include <Path.h>
|
|
||||||
|
|
||||||
#include "Feed.h"
|
|
||||||
#include "Util.h"
|
|
||||||
|
|
||||||
#undef B_TRANSLATION_CONTEXT
|
|
||||||
#define B_TRANSLATION_CONTEXT "LocalSource"
|
|
||||||
|
|
||||||
|
|
||||||
LocalSource::LocalSource()
|
|
||||||
{
|
|
||||||
BDirectory subDir = BDirectory(_SubscriptionPath().Path());
|
|
||||||
if (subDir.InitCheck() == B_ENTRY_NOT_FOUND) {
|
|
||||||
// subDir.CreateDirectory(subPath.Path(), &subDir);
|
|
||||||
|
|
||||||
// subPath.Append("Haiku Project");
|
|
||||||
// Feed defaultSub(BUrl("https://www.haiku-os.org/blog/index.xml"),
|
|
||||||
// BEntry(subPath.Path()));
|
|
||||||
// defaultSub.SetTitle("Haiku Project");
|
|
||||||
// defaultSub.Filetize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BObjectList<Feed>
|
|
||||||
LocalSource::Feeds()
|
|
||||||
{
|
|
||||||
BDirectory subDir = BDirectory(_SubscriptionPath().Path());
|
|
||||||
BEntry feedEntry;
|
|
||||||
BPath feedPath;
|
|
||||||
|
|
||||||
BObjectList<Feed> feeds;
|
|
||||||
|
|
||||||
while (subDir.GetNextEntry(&feedEntry) == B_OK
|
|
||||||
&& feedEntry.GetPath(&feedPath) == B_OK)
|
|
||||||
{
|
|
||||||
Feed* feed = GetFeed(feedPath.Path());
|
|
||||||
feeds.AddItem(feed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return feeds;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Feed*
|
|
||||||
LocalSource::GetFeed(const char* identifier)
|
|
||||||
{
|
|
||||||
BFile file(identifier, B_READ_ONLY);
|
|
||||||
time_t tt_lastDate = 0;
|
|
||||||
BDateTime lastDate;
|
|
||||||
BString title;
|
|
||||||
BString url;
|
|
||||||
BString hash;
|
|
||||||
|
|
||||||
file.ReadAttrString("Feed:name", &title);
|
|
||||||
file.ReadAttrString("META:url", &url);
|
|
||||||
file.ReadAttrString("Feed:hash", &hash);
|
|
||||||
file.ReadAttr("Feed:when", B_TIME_TYPE, 0, &tt_lastDate, sizeof(time_t));
|
|
||||||
lastDate.SetTime_t(tt_lastDate);
|
|
||||||
|
|
||||||
Feed* feed = new Feed(identifier, title.String(), url.String());
|
|
||||||
feed->SetHash(hash);
|
|
||||||
feed->SetLastDate(lastDate);
|
|
||||||
return feed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::Fetch(Feed* feed)
|
|
||||||
{
|
|
||||||
BFile cacheFile = BFile(feed->Identifier(), B_READ_WRITE | B_CREATE_FILE);
|
|
||||||
BString hash;
|
|
||||||
|
|
||||||
int32 result = fetch(feed->Url(), &cacheFile, &hash, 30);
|
|
||||||
feed->SetHash(hash);
|
|
||||||
|
|
||||||
if (result == 0)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::Parse(Feed* feed)
|
|
||||||
{
|
|
||||||
if (IsUpdated(feed) == false)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (_IsAtom(feed) == true)
|
|
||||||
return _AtomParse(feed);
|
|
||||||
else if (_IsRss(feed) == true)
|
|
||||||
return _RssParse(feed);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::AddFeed(Feed* newFeed)
|
|
||||||
{
|
|
||||||
if (newFeed->Identifier().IsEmpty() == true) {
|
|
||||||
BPath subPath = _SubscriptionPath();
|
|
||||||
subPath.Append(urlToFilename(newFeed->Url()));
|
|
||||||
newFeed->SetIdentifier(subPath.Path());
|
|
||||||
}
|
|
||||||
EditFeed(newFeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::EditFeed(Feed* updated)
|
|
||||||
{
|
|
||||||
Feed* old = GetFeed(updated->Identifier());
|
|
||||||
BFile file(updated->Identifier(), B_READ_WRITE | B_CREATE_FILE);
|
|
||||||
|
|
||||||
if (old->Title() != updated->Title()) {
|
|
||||||
BString title = updated->Title();
|
|
||||||
file.WriteAttrString("Feed:name", &title);
|
|
||||||
}
|
|
||||||
if (old->Url() != updated->Url()) {
|
|
||||||
BString url = updated->Url().UrlString();
|
|
||||||
file.WriteAttrString("META:url", &url);
|
|
||||||
}
|
|
||||||
if (old->Hash() != updated->Hash()) {
|
|
||||||
BString hash = updated->Hash();
|
|
||||||
file.WriteAttrString("Feed:hash", &hash);
|
|
||||||
}
|
|
||||||
if (old->Date() != updated->Date()) {
|
|
||||||
time_t tt_date = updated->Date().Time_t();
|
|
||||||
file.WriteAttr("Feed:when", B_TIME_TYPE, 0, &tt_date, sizeof(time_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
BString type("application/x-feed-source");
|
|
||||||
file.WriteAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 0, type.String(),
|
|
||||||
type.CountChars() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::RemoveFeed(Feed* mortonta)
|
|
||||||
{
|
|
||||||
BEntry entry(mortonta->Identifier().String());
|
|
||||||
entry.Remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::IsUpdated(Feed* feed)
|
|
||||||
{
|
|
||||||
return (GetFeed(feed->Identifier())->Hash() != feed->Hash());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_IsAtom(Feed* feed)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLDocument xml;
|
|
||||||
xml.LoadFile(feed->Identifier().String());
|
|
||||||
|
|
||||||
if (xml.FirstChildElement("feed"))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_IsRss(Feed* feed)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLDocument xml;
|
|
||||||
xml.LoadFile(feed->Identifier().String());
|
|
||||||
|
|
||||||
if (xml.FirstChildElement("rss"))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_AtomParse(Feed* atomFeed)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLDocument xml;
|
|
||||||
xml.LoadFile(atomFeed->Identifier().String());
|
|
||||||
|
|
||||||
tinyxml2::XMLElement* xfeed = xml.FirstChildElement("feed");
|
|
||||||
|
|
||||||
_AtomRootParse(atomFeed, xfeed);
|
|
||||||
_AtomEntriesParse(atomFeed, xfeed);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::_AtomRootParse(Feed* feed, tinyxml2::XMLElement* xfeed)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLElement* xentry = xfeed->FirstChildElement("entry");
|
|
||||||
|
|
||||||
bool set = false;
|
|
||||||
|
|
||||||
_SetTitle(feed, xfeed->FirstChildElement("title"));
|
|
||||||
|
|
||||||
set = _SetDate(feed, xfeed->FirstChildElement("updated"));
|
|
||||||
if (!set)
|
|
||||||
set = _SetDate(feed, xfeed->FirstChildElement("published"));
|
|
||||||
if (!set && xentry)
|
|
||||||
set = _SetDate(feed, xentry->FirstChildElement("updated"));
|
|
||||||
if (!set && xentry)
|
|
||||||
set = _SetDate(feed, xentry->FirstChildElement("published"));
|
|
||||||
|
|
||||||
BString logString(B_TRANSLATE("Channel '%source%' at %url%:\n"));
|
|
||||||
logString.ReplaceAll("%source%", feed->Title().String());
|
|
||||||
logString.ReplaceAll("%url%", feed->Url().UrlString());
|
|
||||||
|
|
||||||
std::cout << logString.String();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::_AtomEntriesParse(Feed* feed, tinyxml2::XMLElement* xfeed)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLElement* xentry;
|
|
||||||
|
|
||||||
xentry = xfeed->FirstChildElement("entry");
|
|
||||||
|
|
||||||
int entryCount = _XmlCountSiblings(xentry, "entry");
|
|
||||||
BObjectList<Entry> entries(entryCount, true);
|
|
||||||
|
|
||||||
BString logString(B_TRANSLATE("\t-%count% entries-\n"));
|
|
||||||
BString entryStr;
|
|
||||||
entryStr << entryCount;
|
|
||||||
logString.ReplaceAll("%count%", entryStr);
|
|
||||||
|
|
||||||
std::cout << logString.String();
|
|
||||||
|
|
||||||
while (xentry) {
|
|
||||||
Entry* newEntry = _AtomEntryParse(feed, xentry);
|
|
||||||
if (newEntry != NULL)
|
|
||||||
entries.AddItem(newEntry);
|
|
||||||
xentry = xentry->NextSiblingElement("entry");
|
|
||||||
}
|
|
||||||
|
|
||||||
feed->SetEntries(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Entry*
|
|
||||||
LocalSource::_AtomEntryParse(Feed* feed, tinyxml2::XMLElement* xentry)
|
|
||||||
{
|
|
||||||
Entry* newEntry = new Entry();
|
|
||||||
|
|
||||||
tinyxml2::XMLElement* xcontent = xentry->FirstChildElement("content");
|
|
||||||
tinyxml2::XMLElement* xmedia = xentry->FirstChildElement("media:group");
|
|
||||||
tinyxml2::XMLPrinter xprinter;
|
|
||||||
|
|
||||||
newEntry->SetTitle(xentry->FirstChildElement("title"));
|
|
||||||
newEntry->SetPostUrl(xentry->FirstChildElement("link")->Attribute("href"));
|
|
||||||
newEntry->SetFeedTitle(feed->Title());
|
|
||||||
|
|
||||||
bool set = false;
|
|
||||||
set = newEntry->SetDescription(xentry->FirstChildElement("summary"));
|
|
||||||
if (!set)
|
|
||||||
set = newEntry->SetDescription(xentry->FirstChildElement("description"));
|
|
||||||
if (!set && xmedia)
|
|
||||||
set = newEntry->SetDescription(
|
|
||||||
xmedia->FirstChildElement("media:description"));
|
|
||||||
|
|
||||||
set = _SetDate(newEntry, xentry->FirstChildElement("updated"));
|
|
||||||
if (!set)
|
|
||||||
set = _SetDate(newEntry, xentry->FirstChildElement("published"));
|
|
||||||
|
|
||||||
if (feed->Date() == NULL || feed->Date() < newEntry->Date())
|
|
||||||
feed->SetDate(newEntry->Date());
|
|
||||||
|
|
||||||
if (xcontent) {
|
|
||||||
xcontent->Accept(&xprinter);
|
|
||||||
newEntry->SetContent(xprinter.CStr());
|
|
||||||
}
|
|
||||||
|
|
||||||
return newEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_RssParse(Feed* feed)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLDocument xml;
|
|
||||||
xml.LoadFile(feed->Identifier().String());
|
|
||||||
|
|
||||||
tinyxml2::XMLElement* xchan = xml.FirstChildElement("rss")->FirstChildElement("channel");
|
|
||||||
|
|
||||||
_RssRootParse(feed, xchan);
|
|
||||||
_RssEntriesParse(feed, xchan);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::_RssRootParse(Feed* feed, tinyxml2::XMLElement* xchan)
|
|
||||||
{
|
|
||||||
_SetTitle(feed, xchan->FirstChildElement("title"));
|
|
||||||
_SetDate(feed, xchan->FirstChildElement("lastBuildDate"));
|
|
||||||
|
|
||||||
BString logString(B_TRANSLATE("Channel '%source%' at %url%:\n"));
|
|
||||||
logString.ReplaceAll("%source%", feed->Title().String());
|
|
||||||
logString.ReplaceAll("%url%", feed->Url().UrlString());
|
|
||||||
|
|
||||||
std::cout << logString.String();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
LocalSource::_RssEntriesParse(Feed* feed, tinyxml2::XMLElement* xchan)
|
|
||||||
{
|
|
||||||
tinyxml2::XMLElement* xitem;
|
|
||||||
|
|
||||||
xitem = xchan->FirstChildElement("item");
|
|
||||||
|
|
||||||
int entryCount = _XmlCountSiblings(xitem, "item");
|
|
||||||
BObjectList<Entry> entries = BObjectList<Entry>(entryCount, true);
|
|
||||||
|
|
||||||
BString logString(B_TRANSLATE("\t-%count% entries-\n"));
|
|
||||||
BString entryStr;
|
|
||||||
entryStr << entryCount;
|
|
||||||
logString.ReplaceAll("%count%", entryStr);
|
|
||||||
|
|
||||||
std::cout << logString.String();
|
|
||||||
|
|
||||||
while (xitem) {
|
|
||||||
entries.AddItem(_RssEntryParse(feed, xitem));
|
|
||||||
xitem = xitem->NextSiblingElement("item");
|
|
||||||
}
|
|
||||||
feed->SetEntries(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Entry*
|
|
||||||
LocalSource::_RssEntryParse(Feed* feed, tinyxml2::XMLElement* xitem)
|
|
||||||
{
|
|
||||||
Entry* newEntry = new Entry();
|
|
||||||
|
|
||||||
newEntry->SetTitle(xitem->FirstChildElement("title"));
|
|
||||||
newEntry->SetDescription(xitem->FirstChildElement("description"));
|
|
||||||
_SetDate(newEntry, xitem->FirstChildElement("pubDate"));
|
|
||||||
newEntry->SetPostUrl(xitem->FirstChildElement("link"));
|
|
||||||
newEntry->SetContent(xitem->FirstChildElement("content:encoded"));
|
|
||||||
newEntry->SetFeedTitle(feed->Title());
|
|
||||||
|
|
||||||
if (feed->Date() == NULL || feed->Date() < newEntry->Date())
|
|
||||||
feed->SetDate(newEntry->Date());
|
|
||||||
|
|
||||||
return newEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_SetTitle(Feed* feed, tinyxml2::XMLElement* elem)
|
|
||||||
{
|
|
||||||
if (elem != NULL && feed->Title().IsEmpty() == true)
|
|
||||||
return feed->SetTitle(elem->GetText());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_SetDate(Feed* feed, const char* dateCStr)
|
|
||||||
{
|
|
||||||
if (dateCStr == NULL)
|
|
||||||
return false;
|
|
||||||
return feed->SetDate(feedDateToBDate(dateCStr));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_SetDate(Feed* feed, tinyxml2::XMLElement* elem)
|
|
||||||
{
|
|
||||||
if (elem == NULL)
|
|
||||||
return false;
|
|
||||||
return _SetDate(feed, elem->GetText());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_SetDate(Entry* entry, const char* dateStr)
|
|
||||||
{
|
|
||||||
if (dateStr == NULL)
|
|
||||||
return false;
|
|
||||||
return entry->SetDate(feedDateToBDate(dateStr));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
LocalSource::_SetDate(Entry* entry, tinyxml2::XMLElement* elem)
|
|
||||||
{
|
|
||||||
if (elem == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return _SetDate(entry, elem->GetText());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Count the amount of siblings to an element of given type name
|
|
||||||
int
|
|
||||||
LocalSource::_XmlCountSiblings (tinyxml2::XMLElement* xsibling,
|
|
||||||
const char* sibling_name)
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
while (xsibling) {
|
|
||||||
count++;
|
|
||||||
xsibling = xsibling->NextSiblingElement(sibling_name);
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BPath
|
|
||||||
LocalSource::_SubscriptionPath()
|
|
||||||
{
|
|
||||||
BPath subPath;
|
|
||||||
find_directory(B_USER_SETTINGS_DIRECTORY, &subPath);
|
|
||||||
subPath.Append("Pogger");
|
|
||||||
subPath.Append("Subscriptions");
|
|
||||||
return subPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
|
||||||
* All rights reserved. Distributed under the terms of the MIT license.
|
|
||||||
*/
|
|
||||||
#ifndef LOCALSOURCE_H
|
|
||||||
#define LOCALSOURCE_H
|
|
||||||
|
|
||||||
#include "FeedSource.h"
|
|
||||||
|
|
||||||
class LocalSource : public FeedSource {
|
|
||||||
public:
|
|
||||||
LocalSource();
|
|
||||||
|
|
||||||
static BObjectList<Feed> Feeds();
|
|
||||||
|
|
||||||
static Feed* GetFeed(const char* identifier);
|
|
||||||
|
|
||||||
static bool Fetch(Feed* feed);
|
|
||||||
static bool Parse(Feed* feed);
|
|
||||||
|
|
||||||
static void AddFeed(Feed* newFeed);
|
|
||||||
static void EditFeed(Feed* updated);
|
|
||||||
static void RemoveFeed(Feed* mortonta);
|
|
||||||
|
|
||||||
static bool IsUpdated(Feed* feed);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static bool _IsAtom(Feed* feed);
|
|
||||||
static bool _IsRss(Feed* feed);
|
|
||||||
|
|
||||||
static bool _AtomParse(Feed* feed);
|
|
||||||
static void _AtomRootParse(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
static void _AtomEntriesParse(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
static Entry* _AtomEntryParse(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
|
|
||||||
static bool _RssParse(Feed* feed);
|
|
||||||
static void _RssRootParse(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
static void _RssEntriesParse(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
static Entry* _RssEntryParse(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
|
|
||||||
static bool _SetTitle(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
|
|
||||||
static bool _SetDate(Feed* feed, const char*);
|
|
||||||
static bool _SetDate(Feed* feed, tinyxml2::XMLElement*);
|
|
||||||
static bool _SetDate(Entry* entry, const char*);
|
|
||||||
static bool _SetDate(Entry* entry, tinyxml2::XMLElement*);
|
|
||||||
|
|
||||||
static int _XmlCountSiblings(tinyxml2::XMLElement*, const char*);
|
|
||||||
static BPath _SubscriptionPath();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // LOCALSOURCE_H
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "App.h"
|
#include "App.h"
|
||||||
#include "EntriesView.h"
|
#include "EntriesView.h"
|
||||||
#include "FeedController.h"
|
#include "FeedController.h"
|
||||||
|
#include "FeedListView.h"
|
||||||
#include "FeedsView.h"
|
#include "FeedsView.h"
|
||||||
#include "Notifier.h"
|
#include "Notifier.h"
|
||||||
#include "UpdatesView.h"
|
#include "UpdatesView.h"
|
||||||
|
@ -49,9 +50,10 @@ MainWindow::MessageReceived(BMessage *msg)
|
||||||
{
|
{
|
||||||
switch (msg->what)
|
switch (msg->what)
|
||||||
{
|
{
|
||||||
case kFeedsAddButton:
|
case kFeedsAddNew:
|
||||||
case kFeedsRemoveButton:
|
case kFeedsEnqueueSelected:
|
||||||
case kFeedsEditButton:
|
case kFeedsRemoveSelected:
|
||||||
|
case kFeedsEditSelected:
|
||||||
case kFeedsSelected:
|
case kFeedsSelected:
|
||||||
case kFeedsEdited:
|
case kFeedsEdited:
|
||||||
case kDownloadStart:
|
case kDownloadStart:
|
||||||
|
|
Ŝarĝante…
Reference in New Issue