Completely abstract Sources

This commit is contained in:
Jaidyn Ann 2021-05-14 02:16:22 -05:00
parent d65c834a6f
commit 726d6346a1
23 changed files with 1034 additions and 102 deletions

View File

@ -36,11 +36,13 @@ SRCS = \
src/FeedEditWindow.cpp \ src/FeedEditWindow.cpp \
src/FeedListItem.cpp \ src/FeedListItem.cpp \
src/FeedsView.cpp \ src/FeedsView.cpp \
src/LocalSource.cpp \
src/MainWindow.cpp \ src/MainWindow.cpp \
src/Mimetypes.cpp \ src/Mimetypes.cpp \
src/OpenWithMenu.cpp \ src/OpenWithMenu.cpp \
src/Preferences.cpp \ src/Preferences.cpp \
src/SourceListItem.cpp \
src/SourceManager.cpp \
src/sources/RssAtom.cpp \
src/UpdatesView.cpp \ src/UpdatesView.cpp \
src/Util.cpp src/Util.cpp

View File

@ -34,9 +34,10 @@ SRCS = \
src/Entry.cpp \ src/Entry.cpp \
src/Feed.cpp \ src/Feed.cpp \
src/FeedController.cpp \ src/FeedController.cpp \
src/LocalSource.cpp \
src/Notifier.cpp \ src/Notifier.cpp \
src/Preferences.cpp \ src/Preferences.cpp \
src/SourceManager.cpp \
src/sources/RssAtom.cpp \
src/Util.cpp src/Util.cpp
# Specify the resource definition files to use. Full or relative paths can be # Specify the resource definition files to use. Full or relative paths can be

View File

@ -19,6 +19,7 @@
#include "Mimetypes.h" #include "Mimetypes.h"
#include "Notifier.h" #include "Notifier.h"
#include "Preferences.h" #include "Preferences.h"
#include "SourceManager.h"
#include "Util.h" #include "Util.h"
@ -158,7 +159,8 @@ App::_OpenSourceFile(BMessage* refMessage)
refMessage->FindRef("refs", &entryRef); refMessage->FindRef("refs", &entryRef);
BPath path(&entryRef); BPath path(&entryRef);
FeedEditWindow* window = new FeedEditWindow(BString(path.Path())); FeedEditWindow* window = new FeedEditWindow(path.Path(),
SourceManager::GetSourceOfType("RssAtom")->fConfigPath);
} }

View File

@ -10,8 +10,6 @@
#include <StorageKit.h> #include <StorageKit.h>
#include "Daemon.h"
#include "Preferences.h"
#include "Util.h" #include "Util.h"
@ -31,15 +29,14 @@ Entry::~Entry()
bool bool
Entry::Filetize() Entry::Filetize(const char* outDirPath)
{ {
BFile file(fTitle.String(), B_READ_WRITE); BFile file(fTitle.String(), B_READ_WRITE);
BString outPath = ((App*)be_app)->fPreferences->EntryDir(); BDirectory outDir(outDirPath);
BDirectory outDir(outPath.String());
time_t tt_date = fDate.Time_t(); time_t tt_date = fDate.Time_t();
if (outDir.InitCheck() == B_ENTRY_NOT_FOUND) { if (outDir.InitCheck() == B_ENTRY_NOT_FOUND) {
BDirectory().CreateDirectory(outPath.String(), &outDir); BDirectory().CreateDirectory(outDirPath, &outDir);
} }
outDir.CreateFile(fTitle.String(), &file); outDir.CreateFile(fTitle.String(), &file);
@ -80,15 +77,6 @@ Entry::SetTitle(const char* titleStr)
} }
bool
Entry::SetTitle(tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return SetTitle(elem->GetText());
return false;
}
BString BString
Entry::Description() Entry::Description()
{ {
@ -106,15 +94,6 @@ Entry::SetDescription(const char* descStr)
} }
bool
Entry::SetDescription(tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return SetDescription(elem->GetText());
return false;
}
BString BString
Entry::FeedTitle() Entry::FeedTitle()
{ {
@ -149,15 +128,6 @@ Entry::SetContent(const char* contentStr)
} }
bool
Entry::SetContent(tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return SetContent(elem->GetText());
return false;
}
BString BString
Entry::PostUrl() Entry::PostUrl()
{ {
@ -175,15 +145,6 @@ Entry::SetPostUrl(const char* urlStr)
} }
bool
Entry::SetPostUrl(tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return SetPostUrl(elem->GetText());
return false;
}
BDateTime BDateTime
Entry::Date() Entry::Date()
{ {

View File

@ -11,8 +11,6 @@
#include <String.h> #include <String.h>
#include <Url.h> #include <Url.h>
#include <tinyxml2.h>
class Entry { class Entry {
public: public:
@ -20,26 +18,22 @@ public:
Entry(); Entry();
~Entry(); ~Entry();
bool Filetize(); bool Filetize(const char* outDirPath);
BString Title(); BString Title();
bool SetTitle(const char*); bool SetTitle(const char*);
bool SetTitle(tinyxml2::XMLElement*);
BString Description(); BString Description();
bool SetDescription(const char*); bool SetDescription(const char*);
bool SetDescription(tinyxml2::XMLElement*);
BString FeedTitle(); BString FeedTitle();
bool SetFeedTitle(BString); bool SetFeedTitle(BString);
BString Content(); BString Content();
bool SetContent(const char*); bool SetContent(const char*);
bool SetContent(tinyxml2::XMLElement*);
BString PostUrl(); BString PostUrl();
bool SetPostUrl(const char*); bool SetPostUrl(const char*);
bool SetPostUrl(tinyxml2::XMLElement*);
BDateTime Date(); BDateTime Date();
bool SetDate(BDateTime date); bool SetDate(BDateTime date);
@ -48,6 +42,7 @@ private:
BString fTitle; BString fTitle;
BString fDescription; BString fDescription;
BString fFeedTitle; BString fFeedTitle;
BString fFeedAddOn;
BDateTime fDate; BDateTime fDate;
BString fPostUrl; BString fPostUrl;
BString fContent; BString fContent;

View File

@ -8,8 +8,6 @@
#include <File.h> #include <File.h>
#include <FindDirectory.h> #include <FindDirectory.h>
#include <tinyxml2.h>
#include "Entry.h" #include "Entry.h"
#include "Util.h" #include "Util.h"
@ -21,8 +19,10 @@ Feed::Feed()
} }
Feed::Feed(const char* identifier, const char* title, const char* url) Feed::Feed(const char* identifier, const char* source, const char* title,
const char* url)
: fTitle(BString(title)), : fTitle(BString(title)),
fSource(BString(source)),
fIdentifier(BString(identifier)), fIdentifier(BString(identifier)),
fUrl(BUrl(url)) fUrl(BUrl(url))
{ {
@ -37,7 +37,6 @@ Feed::Feed(Feed* feed)
} }
BObjectList<Entry> BObjectList<Entry>
Feed::Entries() Feed::Entries()
{ {
@ -164,17 +163,32 @@ Feed::SetLastHash(BString hash)
} }
BString const char*
Feed::Identifier() Feed::Identifier()
{ {
return fIdentifier; return fIdentifier.String();
} }
bool bool
Feed::SetIdentifier(BString id) Feed::SetIdentifier(const char* id)
{ {
fIdentifier = id; fIdentifier = BString(id);
return true;
}
const char*
Feed::Source()
{
return fSource.String();
}
bool
Feed::SetSource(const char* source)
{
fSource = BString(source);
return true; return true;
} }

View File

@ -5,9 +5,6 @@
#ifndef FEED_H #ifndef FEED_H
#define FEED_H #define FEED_H
#include <tinyxml2.h>
#include <ObjectList.h> #include <ObjectList.h>
#include "Entry.h" #include "Entry.h"
@ -22,7 +19,8 @@ class BUrl;
class Feed { class Feed {
public: public:
Feed(); Feed();
Feed(const char* identifier, const char* title, const char* url); Feed(const char* identifier, const char* source, const char* title,
const char* url);
Feed(Feed*); Feed(Feed*);
BObjectList<Entry> Entries(); BObjectList<Entry> Entries();
@ -48,8 +46,11 @@ public:
BString LastHash(); BString LastHash();
bool SetLastHash(BString hash); bool SetLastHash(BString hash);
BString Identifier(); const char* Identifier();
bool SetIdentifier(BString id); bool SetIdentifier(const char* id);
const char* Source();
bool SetSource(const char* source);
protected: protected:
BString fTitle; BString fTitle;
@ -57,6 +58,7 @@ protected:
BDateTime fLastDate; // Last time feed was parsed BDateTime fLastDate; // Last time feed was parsed
BUrl fUrl; BUrl fUrl;
BString fIdentifier; BString fIdentifier;
BString fSource;
BString fHash; BString fHash;
BString fLastHash; BString fLastHash;

View File

@ -17,7 +17,8 @@
#include "Daemon.h" #include "Daemon.h"
#include "Entry.h" #include "Entry.h"
#include "LocalSource.h" #include "Preferences.h"
#include "SourceManager.h"
#undef B_TRANSLATION_CONTEXT #undef B_TRANSLATION_CONTEXT
@ -71,7 +72,7 @@ FeedController::MessageReceived(BMessage* msg)
} }
case kUpdateSubscribed: case kUpdateSubscribed:
{ {
BObjectList<Feed> list = LocalSource::Feeds(); BObjectList<Feed> list = SourceManager::Feeds();
fDownloadQueue->AddList(&list); fDownloadQueue->AddList(&list);
_SendProgress(); _SendProgress();
break; break;
@ -193,7 +194,7 @@ FeedController::_DownloadLoop(void* data)
std::cout << B_TRANSLATE("Downloading feed from ") std::cout << B_TRANSLATE("Downloading feed from ")
<< feedBuffer->Url().UrlString() << "\n"; << feedBuffer->Url().UrlString() << "\n";
if (LocalSource::Fetch(feedBuffer)) { if (SourceManager::Fetch(feedBuffer)) {
send_data(main, kDownloadComplete, (void*)feedBuffer, sizeof(Feed)); send_data(main, kDownloadComplete, (void*)feedBuffer, sizeof(Feed));
} }
else { else {
@ -220,16 +221,16 @@ FeedController::_ParseLoop(void* data)
BString feedTitle; BString feedTitle;
BUrl feedUrl = feedBuffer->Url(); BUrl feedUrl = feedBuffer->Url();
LocalSource::Parse(feedBuffer); SourceManager::Parse(feedBuffer);
entries = feedBuffer->NewEntries(); entries = feedBuffer->NewEntries();
entriesCount = entries.CountItems(); entriesCount = entries.CountItems();
feedTitle = feedBuffer->Title(); feedTitle = feedBuffer->Title();
for (int i = 0; i < entriesCount; i++) for (int i = 0; i < entriesCount; i++)
entries.ItemAt(i)->Filetize(); entries.ItemAt(i)->Filetize(((App*)be_app)->fPreferences->EntryDir());
entries.MakeEmpty(); entries.MakeEmpty();
LocalSource::EditFeed(feedBuffer); SourceManager::EditFeed(feedBuffer);
send_data(main, entriesCount, (void*)feedBuffer, sizeof(Feed)); send_data(main, entriesCount, (void*)feedBuffer, sizeof(Feed));
free(feedBuffer); free(feedBuffer);
} }

View File

@ -17,8 +17,8 @@
#include "App.h" #include "App.h"
#include "FeedController.h" #include "FeedController.h"
#include "FeedListItem.h" #include "FeedListItem.h"
#include "SourceManager.h"
#include "FeedsView.h" #include "FeedsView.h"
#include "LocalSource.h"
#include "Util.h" #include "Util.h"
@ -38,11 +38,11 @@ FeedEditWindow::FeedEditWindow()
} }
FeedEditWindow::FeedEditWindow(BString identifier) FeedEditWindow::FeedEditWindow(const char* identifier, const char* source)
: FeedEditWindow() : FeedEditWindow()
{ {
SetTitle(B_TRANSLATE("Edit feed")); SetTitle(B_TRANSLATE("Edit feed"));
fFeed = LocalSource::GetFeed(identifier); fFeed = SourceManager::GetFeed(identifier, source);
fFeedNameText->SetText(fFeed->Title().String()); fFeedNameText->SetText(fFeed->Title().String());
fFeedUrlText->SetText(fFeed->Url().UrlString().String()); fFeedUrlText->SetText(fFeed->Url().UrlString().String());
@ -146,10 +146,10 @@ FeedEditWindow::_SaveFeed()
fFeed->SetTitle(title.String()); fFeed->SetTitle(title.String());
fFeed->SetUrl(BUrl(urlString)); fFeed->SetUrl(BUrl(urlString));
if (fFeed->Identifier().IsEmpty() == true) if (BString(fFeed->Identifier()).IsEmpty() == true)
LocalSource::AddFeed(fFeed); SourceManager::AddFeed(fFeed);
else else
LocalSource::EditFeed(fFeed); SourceManager::EditFeed(fFeed);
BMessage edited(kFeedsEdited); BMessage edited(kFeedsEdited);
// BMessage enqueueUpdated(kEnqueueFeed); // BMessage enqueueUpdated(kEnqueueFeed);

View File

@ -26,7 +26,7 @@ enum
class FeedEditWindow : public BWindow { class FeedEditWindow : public BWindow {
public: public:
FeedEditWindow(); FeedEditWindow();
FeedEditWindow(BString feedIdentifier); FeedEditWindow(const char* identifier, const char* source);
~FeedEditWindow(); ~FeedEditWindow();
void MessageReceived(BMessage* msg); void MessageReceived(BMessage* msg);

View File

@ -21,7 +21,8 @@ FeedListItem::FeedListItem(Feed* feed)
BStringItem(feed->Title().String(), 0, false), BStringItem(feed->Title().String(), 0, false),
fStatus(kClearStatus), fStatus(kClearStatus),
fFeedUrl(feed->Url()), fFeedUrl(feed->Url()),
fFeedIdentifier(feed->Identifier()) fFeedIdentifier(feed->Identifier()),
fFeedSource(feed->Source())
{ {
if (feed->Title().IsEmpty() == true) if (feed->Title().IsEmpty() == true)
SetText(B_TRANSLATE("Untitled Feed")); SetText(B_TRANSLATE("Untitled Feed"));
@ -58,10 +59,17 @@ FeedListItem::DrawItem(BView* owner, BRect frame, bool complete)
} }
BString const char*
FeedListItem::FeedIdentifier() FeedListItem::FeedIdentifier()
{ {
return fFeedIdentifier; return fFeedIdentifier.String();
}
const char*
FeedListItem::FeedSource()
{
return fFeedSource.String();
} }

View File

@ -29,7 +29,8 @@ public:
void DrawItem(BView* owner, BRect frame, bool complete); void DrawItem(BView* owner, BRect frame, bool complete);
BUrl FeedUrl(); BUrl FeedUrl();
BString FeedIdentifier(); const char* FeedIdentifier();
const char* FeedSource();
void SetStatus(int8 status); void SetStatus(int8 status);
@ -37,6 +38,7 @@ private:
int8 fStatus; int8 fStatus;
BUrl fFeedUrl; BUrl fFeedUrl;
BString fFeedIdentifier; BString fFeedIdentifier;
BString fFeedSource;
}; };

View File

@ -10,7 +10,7 @@
#include <Message.h> #include <Message.h>
#include <GroupView.h> #include <GroupView.h>
#include <LayoutBuilder.h> #include <LayoutBuilder.h>
#include <ListView.h> #include <OutlineListView.h>
#include <ScrollView.h> #include <ScrollView.h>
#include <SeparatorView.h> #include <SeparatorView.h>
#include <StringList.h> #include <StringList.h>
@ -21,8 +21,9 @@
#include "FeedController.h" #include "FeedController.h"
#include "FeedEditWindow.h" #include "FeedEditWindow.h"
#include "FeedListItem.h" #include "FeedListItem.h"
#include "LocalSource.h"
#include "Notifier.h" #include "Notifier.h"
#include "SourceListItem.h"
#include "SourceManager.h"
#undef B_TRANSLATION_CONTEXT #undef B_TRANSLATION_CONTEXT
@ -115,7 +116,7 @@ void
FeedsView::_InitInterface() FeedsView::_InitInterface()
{ {
// Feeds list // Feeds list
fFeedsListView = new BListView("feedsList"); fFeedsListView = new BOutlineListView("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));
@ -189,7 +190,8 @@ FeedsView::_EditSelectedFeed()
{ {
int32 selIndex = fFeedsListView->CurrentSelection(); int32 selIndex = fFeedsListView->CurrentSelection();
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex); FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
FeedEditWindow* edit = new FeedEditWindow(selected->FeedIdentifier()); FeedEditWindow* edit = new FeedEditWindow(selected->FeedIdentifier(),
selected->FeedSource());
edit->Show(); edit->Show();
edit->Activate(); edit->Activate();
@ -212,23 +214,33 @@ FeedsView::_RemoveSelectedFeed()
int32 selIndex = fFeedsListView->CurrentSelection(); int32 selIndex = fFeedsListView->CurrentSelection();
FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex); FeedListItem* selected = (FeedListItem*)fFeedsListView->ItemAt(selIndex);
LocalSource::RemoveFeed(LocalSource::GetFeed(selected->FeedIdentifier())); SourceManager::RemoveFeed(SourceManager::GetFeed(selected->FeedIdentifier(),
selected->FeedSource()));
} }
void void
FeedsView::_PopulateFeedList() FeedsView::_PopulateFeedList()
{ {
BObjectList<Feed> feeds = LocalSource::Feeds(); BObjectList<Source> sources = SourceManager::Sources();
int32 selected = fFeedsListView->CurrentSelection(); int32 selected = fFeedsListView->CurrentSelection();
for (int i = fFeedsListView->CountItems(); i >= 0; i--) for (int i = fFeedsListView->CountItems(); i >= 0; i--)
delete ((FeedListItem*)fFeedsListView->RemoveItem(i)); delete ((FeedListItem*)fFeedsListView->RemoveItem(i));
for (int i = 0; i < sources.CountItems(); i++) {
Source* source = sources.ItemAt(i);
SourceListItem* sourceItem = new SourceListItem(source);
fFeedsListView->AddItem(sourceItem);
fFeedsListView->Expand(sourceItem);
BObjectList<Feed> feeds = source->Feeds();
for (int i = 0; i < feeds.CountItems(); i++) { for (int i = 0; i < feeds.CountItems(); i++) {
Feed* feed = feeds.ItemAt(i); Feed* feed = feeds.ItemAt(i);
FeedListItem* item = new FeedListItem(feed); FeedListItem* feedItem = new FeedListItem(feed);
fFeedsListView->AddItem(item); fFeedsListView->AddUnder(feedItem, sourceItem);
}
} }
if (fFeedsListView->CountItems() < selected) if (fFeedsListView->CountItems() < selected)
@ -253,12 +265,14 @@ FeedsView::_UpdateProgress(BMessage* msg, int8 status)
} }
for (int i = 0; i < fFeedsListView->CountItems(); i++) { for (int i = 0; i < fFeedsListView->CountItems(); i++) {
if (fFeedsListView->Superitem(fFeedsListView->ItemAt(i)) != NULL) {
FeedListItem* item = (FeedListItem*)fFeedsListView->ItemAt(i); FeedListItem* item = (FeedListItem*)fFeedsListView->ItemAt(i);
if (item->FeedUrl().UrlString() == feedUrl) { if (item->FeedUrl().UrlString() == feedUrl) {
item->SetStatus(status); item->SetStatus(status);
fFeedsListView->InvalidateItem(i); fFeedsListView->InvalidateItem(i);
} }
} }
}
} }

View File

@ -10,7 +10,7 @@
#include <GroupView.h> #include <GroupView.h>
class BMessage; class BMessage;
class BListView; class BOutlineListView;
class BScrollView; class BScrollView;
class BStringView; class BStringView;
@ -45,7 +45,7 @@ private:
BButton* fRemoveButton; BButton* fRemoveButton;
BButton* fEditButton; BButton* fEditButton;
BStringView* fProgressLabel; BStringView* fProgressLabel;
BListView* fFeedsListView; BOutlineListView* fFeedsListView;
BScrollView* fFeedsScrollView; BScrollView* fFeedsScrollView;
}; };

View File

@ -135,10 +135,10 @@ Preferences::SetUpdateIntervalIndex(int8 index)
} }
BString const char*
Preferences::EntryDir() Preferences::EntryDir()
{ {
return fEntryDir; return fEntryDir.String();
} }

View File

@ -32,7 +32,7 @@ public:
int UpdateIntervalIndex(); int UpdateIntervalIndex();
void SetUpdateIntervalIndex(int8 index); void SetUpdateIntervalIndex(int8 index);
BString EntryDir(); const char* EntryDir();
status_t SetEntryDir(const char* path); status_t SetEntryDir(const char* path);
BString EntryOpenWith(); BString EntryOpenWith();

52
src/Source.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef SOURCE_H
#define SOURCE_H
#include <ObjectList.h>
#include "Feed.h"
class BView;
class Source {
public:
// Constructor should check if the file at configPath exists; if it doesn't,
// then the source is likely being created, and ConfigView() is about to be
// called.
// Source(const char* configPath);
virtual BObjectList<Feed> Feeds() { return BObjectList<Feed>(); };
virtual Feed* GetFeed(const char* identifier) { return new Feed(); };
virtual bool Fetch(Feed* feed) { return false; };
virtual bool Parse(Feed* feed) { return false;};
virtual void AddFeed(Feed* newFeed) { };
virtual void EditFeed(Feed* updated) { };
virtual void RemoveFeed(Feed* mortonta) { };
// Check if the given feed has changed since the last update
virtual bool IsUpdated(Feed* feed) { return false; };
// Will be shown in config window when editing source
virtual BView* ConfigView() { return NULL; };
virtual bool IsConfigurable() { return false; };
virtual int8 MaxOfSource() { return -1; };
// Will be used in the feed edit window
virtual BView* EditFeedView() { return NULL; };
virtual const char* Type() { return "Source"; };
virtual const char* Name() { return "Generic"; };
BString fConfigPath;
};
#endif // SOURCE_H

32
src/SourceListItem.cpp Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "SourceListItem.h"
#include <Catalog.h>
#include <View.h>
#include "Source.h"
#include "Util.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "SourceListItem"
SourceListItem::SourceListItem(Source* source)
: BStringItem(source->Name(), 0, false),
fSource(source)
{
}
Source*
SourceListItem::FeedSource()
{
return fSource;
}

26
src/SourceListItem.h Normal file
View File

@ -0,0 +1,26 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef SOURCEITEM_H
#define SOURCEITEM_H
#include <StringItem.h>
class Source;
class SourceListItem : public BStringItem
{
public:
SourceListItem(Source* source);
Source* FeedSource();
private:
Source* fSource;
};
#endif // SOURCEITEM_H

170
src/SourceManager.cpp Normal file
View File

@ -0,0 +1,170 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "SourceManager.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 "Source.h"
#include "sources/RssAtom.h"
#include "Util.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "SourceManager"
BObjectList<Source>
SourceManager::Sources()
{
BDirectory sourceDir = BDirectory(_SourceConfigPath().Path());
BEntry sourceEntry;
BPath sourcePath;
BObjectList<Source> sources;
while (sourceDir.GetNextEntry(&sourceEntry) == B_OK
&& sourceEntry.GetPath(&sourcePath) == B_OK)
{
Source* source = GetSource(sourcePath.Path());
if (source != NULL)
sources.AddItem(source);
}
// If no sources, create a default RssAtom one
if (sources.CountItems() == 0) {
sourcePath = _SourceConfigPath();
sourcePath.Append("RSS-Atom");
RssAtom(sourcePath.Path());
sources.AddItem(GetSource(sourcePath.Path()));
}
return sources;
}
Source*
SourceManager::GetSource(const char* configPath)
{
BFile config(configPath, B_READ_ONLY);
BMessage storage;
storage.Unflatten(&config);
BString sourceType;
if (storage.FindString("source_type", &sourceType) == B_OK)
if (sourceType == BString("RssAtom"))
return new RssAtom(configPath);
return NULL;
}
Source*
SourceManager::GetSource(Feed* feed)
{
return GetSource(feed->Source());
}
Source*
SourceManager::GetSourceOfType(const char* sourceType)
{
BObjectList<Source> sources = Sources();
for (int i = 0; i < sources.CountItems(); i++) {
if (sources.ItemAt(i)->Type() == BString(sourceType))
return sources.ItemAt(i);
}
return NULL;
}
BObjectList<Feed>
SourceManager::Feeds()
{
BObjectList<Source> sources = Sources();
BObjectList<Feed> feeds;
for (int i = 0; i < sources.CountItems(); i++) {
Source* sauce = sources.ItemAt(i);
BObjectList<Feed> newFeeds = sources.ItemAt(i)->Feeds();
feeds.AddList(&newFeeds);
}
return feeds;
}
Feed*
SourceManager::GetFeed(const char* identifier, const char* source)
{
return GetSource(source)->GetFeed(identifier);
}
bool
SourceManager::Fetch(Feed* feed)
{
return GetSource(feed)->Fetch(feed);
}
bool
SourceManager::Parse(Feed* feed)
{
return GetSource(feed)->Parse(feed);
}
void
SourceManager::AddFeed(Feed* newFeed)
{
return GetSource(newFeed)->AddFeed(newFeed);
}
void
SourceManager::EditFeed(Feed* updated)
{
return GetSource(updated)->EditFeed(updated);
}
void
SourceManager::RemoveFeed(Feed* mortonta)
{
return GetSource(mortonta)->EditFeed(mortonta);
}
bool
SourceManager::IsUpdated(Feed* feed)
{
return GetSource(feed)->IsUpdated(feed);
}
BPath
SourceManager::_SourceConfigPath()
{
BPath sourcePath;
find_directory(B_USER_SETTINGS_DIRECTORY, &sourcePath);
sourcePath.Append("Pogger");
sourcePath.Append("Sources");
BDirectory sourceDir = BDirectory(sourcePath.Path());
if (sourceDir.InitCheck() == B_ENTRY_NOT_FOUND)
sourceDir.CreateDirectory(sourcePath.Path(), &sourceDir);
return sourcePath;
}

42
src/SourceManager.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef SOURCEMANAGER_H
#define SOURCEMANAGER_H
#include <ObjectList.h>
#include "Source.h"
class Feed;
class SourceManager {
public:
static BObjectList<Source> Sources();
static Source* GetSource(const char* configPath);
static Source* GetSource(Feed* feed);
static Source* GetSourceOfType(const char* sourceType);
static BObjectList<Feed> Feeds();
static Feed* GetFeed(const char* identifier, const char* source);
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 BPath _SourceConfigPath();
};
#endif // SOURCEMANAGER_H

529
src/sources/RssAtom.cpp Normal file
View File

@ -0,0 +1,529 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "RssAtom.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 "RssAtom"
RssAtom::RssAtom(const char* configPath)
: fConfigPath(BString(configPath))
{
_LoadConfig();
_EnsureSubscriptions();
}
const char*
RssAtom::Type()
{
return "RssAtom";
}
const char*
RssAtom::Name()
{
return fTitle.String();
}
BObjectList<Feed>
RssAtom::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*
RssAtom::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, fConfigPath.String(), title.String(),
url.String());
feed->SetHash(hash);
feed->SetLastDate(lastDate);
return feed;
}
bool
RssAtom::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
RssAtom::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
RssAtom::AddFeed(Feed* newFeed)
{
if (BString(newFeed->Identifier()).IsEmpty() == true) {
BPath subPath = _SubscriptionPath();
subPath.Append(urlToFilename(newFeed->Url()));
newFeed->SetIdentifier(subPath.Path());
}
EditFeed(newFeed);
}
void
RssAtom::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
RssAtom::RemoveFeed(Feed* mortonta)
{
BEntry entry(mortonta->Identifier());
entry.Remove();
}
bool
RssAtom::IsUpdated(Feed* feed)
{
return (GetFeed(feed->Identifier())->Hash() != feed->Hash());
}
bool
RssAtom::_IsAtom(Feed* feed)
{
tinyxml2::XMLDocument xml;
xml.LoadFile(feed->Identifier());
if (xml.FirstChildElement("feed"))
return true;
return false;
}
bool
RssAtom::_IsRss(Feed* feed)
{
tinyxml2::XMLDocument xml;
xml.LoadFile(feed->Identifier());
if (xml.FirstChildElement("rss"))
return true;
return false;
}
bool
RssAtom::_AtomParse(Feed* atomFeed)
{
tinyxml2::XMLDocument xml;
xml.LoadFile(atomFeed->Identifier());
tinyxml2::XMLElement* xfeed = xml.FirstChildElement("feed");
_AtomRootParse(atomFeed, xfeed);
_AtomEntriesParse(atomFeed, xfeed);
return true;
}
void
RssAtom::_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
RssAtom::_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*
RssAtom::_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;
_SetTitle(newEntry, xentry->FirstChildElement("title"));
newEntry->SetPostUrl(xentry->FirstChildElement("link")->Attribute("href"));
newEntry->SetFeedTitle(feed->Title());
bool set = false;
set = _SetDescription(newEntry, xentry->FirstChildElement("summary"));
if (!set)
set = _SetDescription(newEntry, xentry->FirstChildElement("description"));
if (!set && xmedia)
set = _SetDescription(newEntry,
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
RssAtom::_RssParse(Feed* feed)
{
tinyxml2::XMLDocument xml;
xml.LoadFile(feed->Identifier());
tinyxml2::XMLElement* xchan = xml.FirstChildElement("rss")->FirstChildElement("channel");
_RssRootParse(feed, xchan);
_RssEntriesParse(feed, xchan);
return true;
}
void
RssAtom::_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
RssAtom::_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*
RssAtom::_RssEntryParse(Feed* feed, tinyxml2::XMLElement* xitem)
{
Entry* newEntry = new Entry();
_SetTitle(newEntry, xitem->FirstChildElement("title"));
_SetDescription(newEntry, xitem->FirstChildElement("description"));
_SetDate(newEntry, xitem->FirstChildElement("pubDate"));
_SetPostUrl(newEntry, xitem->FirstChildElement("link"));
_SetContent(newEntry, xitem->FirstChildElement("content:encoded"));
newEntry->SetFeedTitle(feed->Title());
if (feed->Date() == NULL || feed->Date() < newEntry->Date())
feed->SetDate(newEntry->Date());
return newEntry;
}
bool
RssAtom::_SetTitle(Feed* feed, tinyxml2::XMLElement* elem)
{
if (elem != NULL && feed->Title().IsEmpty() == true)
return feed->SetTitle(elem->GetText());
return false;
}
bool
RssAtom::_SetTitle(Entry* entry, tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return entry->SetTitle(elem->GetText());
return false;
}
bool
RssAtom::_SetDate(Feed* feed, const char* dateCStr)
{
if (dateCStr == NULL)
return false;
return feed->SetDate(feedDateToBDate(dateCStr));
}
bool
RssAtom::_SetDate(Feed* feed, tinyxml2::XMLElement* elem)
{
if (elem == NULL)
return false;
return _SetDate(feed, elem->GetText());
}
bool
RssAtom::_SetDate(Entry* entry, const char* dateStr)
{
if (dateStr == NULL)
return false;
return entry->SetDate(feedDateToBDate(dateStr));
}
bool
RssAtom::_SetDate(Entry* entry, tinyxml2::XMLElement* elem)
{
if (elem == NULL)
return false;
return _SetDate(entry, elem->GetText());
}
bool
RssAtom::_SetDescription(Entry* entry, tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return entry->SetDescription(elem->GetText());
return false;
}
bool
RssAtom::_SetContent(Entry* entry, tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return entry->SetContent(elem->GetText());
return false;
}
bool
RssAtom::_SetPostUrl(Entry* entry, tinyxml2::XMLElement* elem)
{
if (elem != NULL)
return entry->SetPostUrl(elem->GetText());
return false;
}
// Count the amount of siblings to an element of given type name
int
RssAtom::_XmlCountSiblings (tinyxml2::XMLElement* xsibling,
const char* sibling_name)
{
int count = 0;
while (xsibling) {
count++;
xsibling = xsibling->NextSiblingElement(sibling_name);
}
return count;
}
void
RssAtom::_LoadConfig()
{
BMessage storage;
BFile configFile = BFile(fConfigPath.String(), B_READ_ONLY);
if (configFile.InitCheck() != B_OK) {
_CreateConfig();
return;
}
storage.Unflatten(&configFile);
fTitle = storage.GetString("source_name", "Untitled RssAtom Source");
}
void
RssAtom::_CreateConfig()
{
std::cout << "CREATNIG\n";
BMessage storage;
BFile configFile = BFile(fConfigPath.String(), B_READ_WRITE|B_CREATE_FILE);
storage.AddString("source_type", "RssAtom");
storage.AddString("source_name", "Atom/RSS");
storage.Flatten(&configFile);
}
void
RssAtom::_EnsureSubscriptions()
{
BPath subPath = _SubscriptionPath();
BDirectory subDir = BDirectory(subPath.Path());
// Create "Subscriptions" dir and add default subscription
if (subDir.InitCheck() == B_ENTRY_NOT_FOUND) {
subDir.CreateDirectory(subPath.Path(), &subDir);
subPath.Append("Haiku Project");
Feed defaultSub(subPath.Path(), fConfigPath.String(), "Haiku Project",
"https://www.haiku-os.org/blog/index.xml");
AddFeed(&defaultSub);
}
}
BPath
RssAtom::_SubscriptionPath()
{
BPath subPath;
find_directory(B_USER_SETTINGS_DIRECTORY, &subPath);
subPath.Append("Pogger");
subPath.Append("Subscriptions");
return subPath;
}

79
src/sources/RssAtom.h Normal file
View File

@ -0,0 +1,79 @@
/*
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef RSSATOM_H
#define RSSATOM_H
#include <tinyxml2.h>
#include <Path.h>
#include "../Source.h"
class Entry;
class RssAtom : public Source {
public:
RssAtom(const char* configPath);
const char* Type();
const char* Name();
BObjectList<Feed> Feeds();
Feed* GetFeed(const char* identifier);
bool Fetch(Feed* feed);
bool Parse(Feed* feed);
void AddFeed(Feed* newFeed);
void EditFeed(Feed* updated);
void RemoveFeed(Feed* mortonta);
bool IsUpdated(Feed* feed);
private:
bool _IsAtom(Feed* feed);
bool _IsRss(Feed* feed);
bool _AtomParse(Feed* feed);
void _AtomRootParse(Feed* feed, tinyxml2::XMLElement*);
void _AtomEntriesParse(Feed* feed, tinyxml2::XMLElement*);
Entry* _AtomEntryParse(Feed* feed, tinyxml2::XMLElement*);
bool _RssParse(Feed* feed);
void _RssRootParse(Feed* feed, tinyxml2::XMLElement*);
void _RssEntriesParse(Feed* feed, tinyxml2::XMLElement*);
Entry* _RssEntryParse(Feed* feed, tinyxml2::XMLElement*);
// These _Sets could probably be better served as a macro :P
bool _SetTitle(Feed* feed, tinyxml2::XMLElement*);
bool _SetTitle(Entry* entry, tinyxml2::XMLElement*);
bool _SetDate(Feed* feed, const char*);
bool _SetDate(Feed* feed, tinyxml2::XMLElement*);
bool _SetDate(Entry* entry, const char*);
bool _SetDate(Entry* entry, tinyxml2::XMLElement*);
bool _SetDescription(Entry* entry, tinyxml2::XMLElement*);
bool _SetContent(Entry* entry, tinyxml2::XMLElement*);
bool _SetPostUrl(Entry* entry, tinyxml2::XMLElement*);
int _XmlCountSiblings(tinyxml2::XMLElement*, const char*);
void _LoadConfig();
void _CreateConfig();
void _EnsureSubscriptions();
BPath _SubscriptionPath();
BString fTitle;
BString fConfigPath;
};
#endif // RSSATOM_H