Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@ -0,0 +1,51 @@
#ifndef NULLSOFT_ATOMPARSEH
#define NULLSOFT_ATOMPARSEH
#if 0
#include "RFCDate.h"
#include "XMLNode.h"
#include "Feeds.h"
#include "../nu/AutoChar.h"
#include "ChannelSync.h"
void ReadAtomItem(XMLNode *item, Channel &channel)
{
}
void ReadAtomChannel(XMLNode *node, Channel &newChannel)
{
XMLNode *curNode = 0;
curNode = node->Get(L"title");
if (curNode)
newChanneltitle = curNode->content;
curNode = node->Get(L"subtitle");
if (curNode)
newChannel.description = curNode->content;
XMLNode::NodeList &links = node->GetList(L"link");
XMLNode::NodeList::iterator linkItr;
for (linkItr=links.begin();linkItr!=links.end();linkItr++)
{
if ((*linkItr)->properties[L"rel"].empty()
|| (*linkItr)->properties[L"rel"]== L"alternate")
newChannel.link = (*linkItr)->properties[L"href"];
}
XMLNode::NodeList &entries = node->GetList(L"entry");
XMLNode::NodeList::iterator entryItr;
for (entryItr=entries.begin();entryItr!=entries.end();entryItr++)
{
}
}
void ReadAtom(XMLNode *atom, ChannelSync *sync)
{
sync->BeginChannelSync();
Channel newChannel;
ReadAtomChannel(atom, newChannel);
sync->NewChannel(newChannel);
sync->EndChannelSync();
}
#endif
#endif

View File

@ -0,0 +1,338 @@
#include <vector>
#include <atomic>
#include "Main.h"
#include "Downloaded.h"
#include "BackgroundDownloader.h"
#include "Feeds.h"
#include "DownloadStatus.h"
#include "DownloadsDialog.h"
#include "api__ml_wire.h"
#include "api/service/waServiceFactory.h"
#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
using namespace Nullsoft::Utility;
#define SIMULTANEOUS_DOWNLOADS 2
std::vector<DownloadToken> downloadsQueue;
LockGuard downloadsQueueLock;
class DownloaderCallback : public ifc_downloadManagerCallback
{
public:
DownloaderCallback( const wchar_t *url, const wchar_t *destination_filepath, const wchar_t *channel, const wchar_t *item, __time64_t publishDate )
{
this->hFile = INVALID_HANDLE_VALUE;
this->url = _wcsdup( url );
this->destination_filepath = _wcsdup( destination_filepath );
this->channel = _wcsdup( channel );
this->item = _wcsdup( item );
this->publishDate = publishDate;
this->totalSize = 0;
this->downloaded = 0;
}
void OnInit(DownloadToken token)
{
// ---- Inform the download status service of our presence----
downloadStatus.AddDownloadThread(token, this->channel, this->item, this->destination_filepath);
}
void OnConnect(DownloadToken token)
{
// ---- retrieve total size
api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver(token);
if (http)
{
this->totalSize = http->content_length();
}
// ---- create file handle
hFile = CreateFile(destination_filepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if ( hFile == INVALID_HANDLE_VALUE )
{
WAC_API_DOWNLOADMANAGER->CancelDownload(token);
}
}
void OnData(DownloadToken token, void *data, size_t datalen)
{
// ---- OnConnect copied here due to dlmgr OnData called first
// ---- retrieve total size
api_httpreceiver *http = WAC_API_DOWNLOADMANAGER->GetReceiver(token);
if ( !this->totalSize && http )
{
this->totalSize = http->content_length();
}
if ( hFile == INVALID_HANDLE_VALUE )
{
// ---- create file handle
hFile = CreateFile(destination_filepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if ( hFile == INVALID_HANDLE_VALUE )
{
WAC_API_DOWNLOADMANAGER->CancelDownload(token);
return;
}
}
// ---- OnConnect to be removed once dlmgr is fixed
// ---- OnData
// ---- if file handle is invalid, then cancel download
if ( hFile == INVALID_HANDLE_VALUE )
{
WAC_API_DOWNLOADMANAGER->CancelDownload(token);
return;
}
this->downloaded = (size_t)WAC_API_DOWNLOADMANAGER->GetBytesDownloaded(token);
if ( datalen > 0 )
{
// ---- hFile is valid handle, and write to disk
DWORD numWritten = 0;
WriteFile(hFile, data,(DWORD)datalen, &numWritten, FALSE);
// ---- failed writing the number of datalen characters, cancel download
if (numWritten != datalen)
{
WAC_API_DOWNLOADMANAGER->CancelDownload(token);
return;
}
}
// if killswitch is turned on, then cancel download
if ( downloadStatus.UpdateStatus(token, this->downloaded, this->totalSize) )
{
WAC_API_DOWNLOADMANAGER->CancelDownload(token);
}
}
void OnCancel(DownloadToken token)
{
if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle(hFile);
DeleteFile(destination_filepath);
}
DownloadsUpdated(token,NULL);
downloadStatus.DownloadThreadDone(token);
{
AutoLock lock( downloadsQueueLock );
size_t l_index = 0;
for ( DownloadToken &l_download_token : downloadsQueue )
{
if ( l_download_token == token )
{
downloadsQueue.erase( downloadsQueue.begin() + l_index );
break;
}
++l_index;
}
}
for ( DownloadToken &l_download_token : downloadsQueue )
{
if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) )
{
WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token );
break;
}
}
this->Release();
}
void OnError(DownloadToken token, int error)
{
if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle(hFile);
DeleteFile(destination_filepath);
}
DownloadsUpdated(token,NULL);
downloadStatus.DownloadThreadDone(token);
{
AutoLock lock(downloadsQueueLock);
for (size_t index = 0; index < downloadsQueue.size(); index++)
{
if (downloadsQueue.at(index) == token)
{
downloadsQueue.erase(downloadsQueue.begin() + index);
break;
}
}
for (size_t index = 0; index < downloadsQueue.size(); index++)
{
if(WAC_API_DOWNLOADMANAGER->IsPending(downloadsQueue.at(index)))
{
WAC_API_DOWNLOADMANAGER->ResumePendingDownload(downloadsQueue.at(index));
break;
}
}
}
this->Release();
}
void OnFinish( DownloadToken token )
{
if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle( hFile );
DownloadedFile *data = new DownloadedFile( this->url, this->destination_filepath, this->channel, this->item, this->publishDate );
data->bytesDownloaded = this->downloaded;
data->totalSize = this->totalSize;
{
AutoLock lock( downloadedFiles );
downloadedFiles.downloadList.push_back( *data );
addToLibrary_thread( *data );
AddPodcastData( *data );
DownloadsUpdated( token, &downloadedFiles.downloadList[ downloadedFiles.downloadList.size() - 1 ] );
}
downloadStatus.DownloadThreadDone( token );
delete data;
}
else
{
DownloadsUpdated( token, NULL );
downloadStatus.DownloadThreadDone( token );
}
{
AutoLock lock( downloadsQueueLock );
size_t l_index = 0;
for ( DownloadToken &l_download_token : downloadsQueue )
{
if ( l_download_token == token )
{
downloadsQueue.erase( downloadsQueue.begin() + l_index );
break;
}
++l_index;
}
for ( DownloadToken &l_download_token : downloadsQueue )
{
if ( WAC_API_DOWNLOADMANAGER->IsPending( l_download_token ) )
{
WAC_API_DOWNLOADMANAGER->ResumePendingDownload( l_download_token );
break;
}
}
}
this->Release();
}
int GetSource( wchar_t *source, size_t source_cch )
{
return wcscpy_s( source, source_cch, this->channel );
}
int GetTitle( wchar_t *title, size_t title_cch )
{
return wcscpy_s( title, title_cch, this->item );
}
int GetLocation( wchar_t *location, size_t location_cch )
{
return wcscpy_s( location, location_cch, this->destination_filepath );
}
size_t AddRef()
{
return _ref_count.fetch_add( 1 );
}
size_t Release()
{
if ( _ref_count.load() == 0 )
return _ref_count.load();
LONG r = _ref_count.fetch_sub( 1 );
if ( r == 0 )
delete( this );
return r;
}
private: // private destructor so no one accidentally calls delete directly on this reference counted object
~DownloaderCallback()
{
if ( url )
free( url );
if ( destination_filepath )
free( destination_filepath );
if ( channel )
free( channel );
if ( item )
free( item );
}
protected:
RECVS_DISPATCH;
private:
HANDLE hFile;
wchar_t *url;
wchar_t *destination_filepath;
wchar_t *channel;
wchar_t *item;
__time64_t publishDate;
size_t totalSize;
size_t downloaded;
std::atomic<std::size_t> _ref_count = 1;
};
void BackgroundDownloader::Download( const wchar_t *url, const wchar_t *savePath, const wchar_t *channel, const wchar_t *item, __time64_t publishDate )
{
DownloaderCallback *callback = new DownloaderCallback( url, savePath, channel, item, publishDate );
{
Nullsoft::Utility::AutoLock lock( downloadsQueueLock );
if ( downloadsQueue.size() < SIMULTANEOUS_DOWNLOADS )
{
DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( AutoChar( url ), callback, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_UI );
downloadsQueue.push_back( dt );
}
else
{
DownloadToken dt = WAC_API_DOWNLOADMANAGER->DownloadEx( AutoChar( url ), callback, api_downloadManager::DOWNLOADEX_CALLBACK | api_downloadManager::DOWNLOADEX_PENDING | api_downloadManager::DOWNLOADEX_UI );
downloadsQueue.push_back( dt );
}
}
}
BackgroundDownloader downloader;
#define CBCLASS DownloaderCallback
START_DISPATCH;
VCB( IFC_DOWNLOADMANAGERCALLBACK_ONFINISH, OnFinish )
VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCANCEL, OnCancel )
VCB( IFC_DOWNLOADMANAGERCALLBACK_ONERROR, OnError )
VCB( IFC_DOWNLOADMANAGERCALLBACK_ONDATA, OnData )
VCB( IFC_DOWNLOADMANAGERCALLBACK_ONCONNECT, OnConnect )
VCB( IFC_DOWNLOADMANAGERCALLBACK_ONINIT, OnInit )
CB( IFC_DOWNLOADMANAGERCALLBACK_GETSOURCE, GetSource )
CB( IFC_DOWNLOADMANAGERCALLBACK_GETTITLE, GetTitle )
CB( IFC_DOWNLOADMANAGERCALLBACK_GETLOCATION, GetLocation )
CB( ADDREF, AddRef )
CB( RELEASE, Release )
END_DISPATCH;
#undef CBCLASS

View File

@ -0,0 +1,18 @@
#ifndef NULLSOFT_BACKGROUNDDOWNLOADERH
#define NULLSOFT_BACKGROUNDDOWNLOADERH
#include <windows.h>
class BackgroundDownloader
{
public:
//void SetSpeed(int kilobytesPerSecond);
void Download(const wchar_t *url, const wchar_t *savePath,
const wchar_t *channel, const wchar_t *item, __time64_t publishDate);
//void Shutdown();
};
extern BackgroundDownloader downloader;
#endif

View File

@ -0,0 +1,14 @@
#ifndef NULLSOFT_CHANNELCHECKH
#define NULLSOFT_CHANNELCHECKH
#include "ChannelSync.h"
class ChannelCheck : public ChannelSync
{
public:
void NewChannel(const Channel &newChannel)
{
channel=newChannel;
}
Channel channel;
};
#endif

View File

@ -0,0 +1,33 @@
#include "main.h"
#include "ChannelRefresher.h"
#include <algorithm>
#include "./subscriptionView.h"
using namespace Nullsoft::Utility;
void ChannelRefresher::BeginChannelSync()
{}
void ChannelRefresher::NewChannel(const Channel &newChannel)
{
AutoLock lock (channels LOCKNAME("ChannelRefresher::NewChannel"));
ChannelList::iterator found;
for (found=channels.begin();found!=channels.end(); found++)
{
if (!wcscmp(found->url, newChannel.url))
break;
}
if (found != channels.end())
{
// todo, redo category indexing as necessary.
found->UpdateFrom(newChannel);
found->lastUpdate = _time64(0);
found->needsRefresh = false;
}
}
void ChannelRefresher::EndChannelSync()
{
HWND wnd = SubscriptionView_FindWindow();
SubscriptionView_RefreshChannels(wnd, TRUE);
}

View File

@ -0,0 +1,14 @@
#ifndef NULLSOFT_CHANNELREFRESHERH
#define NULLSOFT_CHANNELREFRESHERH
#include "ChannelSync.h"
class ChannelRefresher: public ChannelSync
{
public:
void BeginChannelSync();
void NewChannel(const Channel &newChannel);
void EndChannelSync();
};
#endif

View File

@ -0,0 +1,31 @@
#ifndef NULLSOFT_CHANNELSYNCH
#define NULLSOFT_CHANNELSYNCH
#include "Feeds.h"
/*
ChannelSync is a virtual base class (aka interface) used by the RSS downloader.
When you instantiate a downloader, you are required to give it a pointer to this interface.
The downloader will call:
BeginChannelSync();
for (;;) // however many it encounters
NewChannel(newChannel); // called once for each channel it downloads
EndChannelSync();
If you have a class or data structure that wants updates, you'll have to mix-in
this interface or implement a data-structure-babysitter class (or however you want to deal with it)
See the "WireManager" for an example of how to use it
*/
class ChannelSync
{
public:
virtual void BeginChannelSync() {}
virtual void NewChannel(const Channel &newChannel) = 0;
virtual void EndChannelSync() {}}
;
#endif

View File

@ -0,0 +1,235 @@
#include "main.h"
#include "api__ml_wire.h"
#include "Cloud.h"
#include "FeedParse.h"
#include "Defaults.h"
#include "./subscriptionView.h"
#include "ChannelRefresher.h"
#include "Util.h"
#include <algorithm>
#include <strsafe.h>
/* benski> TODO rewrite Callback() so we don't have to reserve a thread */
using namespace Nullsoft::Utility;
#define CLOUD_TICK_MS 60000
ChannelRefresher channelRefresher;
static bool kill = false;
static __time64_t RetrieveMinimalUpdateTime()
{
__time64_t minUpdateTime = 0;
AutoLock lock (channels LOCKNAME("RetrieveMinimalUpdateTime"));
ChannelList::iterator itr;
for (itr=channels.begin(); itr!=channels.end(); itr++)
{
if (itr->useDefaultUpdate)
{
if ( !updateTime ) autoUpdate = 0;
if ( autoUpdate && (!minUpdateTime || minUpdateTime && (updateTime < minUpdateTime)) )
{
minUpdateTime = updateTime;
}
}
else // use the custom values
{
if ( !itr->updateTime ) itr->autoUpdate = 0;
if ( itr->autoUpdate && (!minUpdateTime || minUpdateTime && (itr->updateTime < minUpdateTime)) )
{
minUpdateTime = itr->updateTime;
}
}
}
return minUpdateTime;
}
int Cloud::CloudThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
{
Cloud *cloud = (Cloud *)user_data;
if (kill)
{
WASABI_API_THREADPOOL->RemoveHandle(0, cloud->cloudEvent);
CloseHandle(cloud->cloudEvent);
WASABI_API_THREADPOOL->RemoveHandle(0, cloud->cloudTimerEvent);
cloud->cloudTimerEvent.Close();
SetEvent(cloud->cloudDone);
return 0;
}
cloud->Callback();
// set waitable timer, overwrite previouse value if any
if (!kill)
{
__time64_t timeToWait = RetrieveMinimalUpdateTime();
if ( timeToWait )
cloud->cloudTimerEvent.Wait(timeToWait * 1000);
}
return 0;
}
Cloud::Cloud() : cloudThread(0), cloudEvent(0), statusText(0)
{}
Cloud::~Cloud()
{
free(statusText);
}
void Cloud::Quit()
{
cloudDone= CreateEvent(NULL, FALSE, FALSE, NULL);
kill = true;
SetEvent(cloudEvent);
WaitForSingleObject(cloudDone, INFINITE);
CloseHandle(cloudDone);
}
void Cloud::RefreshAll()
{
AutoLock lock (channels LOCKNAME("RefreshAll"));
ChannelList::iterator itr;
for (itr = channels.begin();itr != channels.end();itr++)
{
itr->needsRefresh = true;
}
}
void Cloud::Init()
{
// setup a periodic callback so we can check on our times
cloudEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
cloudThread = WASABI_API_THREADPOOL->ReserveThread(0);
WASABI_API_THREADPOOL->AddHandle(cloudThread, cloudEvent, CloudThreadPoolFunc, this, 0, 0);
WASABI_API_THREADPOOL->AddHandle(cloudThread, cloudTimerEvent, CloudThreadPoolFunc, this, 1, 0);
__time64_t timeToWait = RetrieveMinimalUpdateTime();
if ( timeToWait )
cloudTimerEvent.Wait(timeToWait * 1000);
}
void Cloud::Refresh(Channel &channel)
{
wchar_t lang_buf[1024] = {0};
WASABI_API_LNGSTRINGW_BUF(IDS_RECEIVING_UPDATES_FOR, lang_buf, 1024);
if (channel.title)
StringCbCat(lang_buf, sizeof(lang_buf), channel.title);
else
lang_buf[0]=0;
SetStatus(lang_buf);
size_t oldSize = channel.items.size();
FeedParse downloader(&channelRefresher, false);
downloader.DownloadURL(channel.url);
if (channel.items.size() > oldSize)
{
WASABI_API_LNGSTRINGW_BUF(IDS_GOT_NEW_ITEMS_FOR, lang_buf, 1024);
StringCbCat(lang_buf, sizeof(lang_buf), channel.title);
SetStatus(lang_buf);
}
else
SetStatus(L"");
}
void Cloud::GetStatus(wchar_t *status, size_t len)
{
AutoLock lock (statusGuard);
if (statusText)
StringCchCopy(status, len, statusText);
else
status[0]=0;
}
void Cloud::SetStatus(const wchar_t *newStatus)
{
AutoLock lock (statusGuard);
free(statusText);
statusText = _wcsdup(newStatus);
HWND hView = SubscriptionView_FindWindow();
if (NULL != hView)
SubscriptionView_SetStatus(hView, statusText);
}
/* --- Private Methods of class Cloud --- */
static void ForceLastUpdate(const Channel &channel)
{
AutoLock lock (channels LOCKNAME("ChannelRefresher::NewChannel"));
ChannelList::iterator found;
for (found=channels.begin();found!=channels.end(); found++)
{
if (!wcscmp(found->url, channel.url))
break;
}
if (found != channels.end())
{
found->lastUpdate = _time64(0);
found->needsRefresh = false;
}
}
/*
@private
checks all channels and updates any that requiring refreshing.
*/
void Cloud::Callback()
{
__time64_t curTime = _time64(0);
size_t i = 0;
Channel temp;
bool refreshed = false;
while (true) // we need to lock the channels object before we check its size, etc, so we can't just use a "for" loop.
{
{ // we want to minimize how long we have to lock, so we'll make a copy of the channel data
AutoLock lock (channels LOCKNAME("Callback"));
if (i >= channels.size())
break;
temp = channels[i]; // make a copy the data so we can safely release the lock
channels[i].needsRefresh = false; // have to set this now. if the site is down or 404, then the refresh will never "complete".
} // end locking scope
if (temp.needsRefresh) // need an immediate refresh? (usually set when the user clicks refresh or update-on-launch is on)
{
Refresh(temp);
refreshed = true;
}
else if (temp.useDefaultUpdate) // this flag is set unless the user chose custom update values
{
if (!updateTime) autoUpdate = 0;
if (autoUpdate && (temp.lastUpdate + updateTime) <= curTime)
{
Refresh(temp);
ForceLastUpdate(temp);
refreshed = true;
}
}
else // use the custom values
{
if (temp.updateTime == 0) temp.autoUpdate = 0;
if (temp.autoUpdate && (temp.lastUpdate + temp.updateTime) <= curTime)
{
Refresh(temp);
ForceLastUpdate(temp);
refreshed = true;
}
}
i++;
}
// if we're refreshing then save out
if (refreshed)
{
SaveAll();
}
}

View File

@ -0,0 +1,35 @@
#ifndef NULLSOFT_CLOUDH
#define NULLSOFT_CLOUDH
#include <windows.h>
#include "Feeds.h"
#include "../nu/threadpool/api_threadpool.h"
#include "../nu/threadpool/timerhandle.hpp"
#include "../nu/AutoLock.h"
class Cloud
{
public:
Cloud();
~Cloud();
void Init();
void Quit();
void Refresh( Channel &channel );
void GetStatus( wchar_t *status, size_t len );
void RefreshAll();
void Pulse() { SetEvent( cloudEvent ); }
private:
static DWORD WINAPI CloudThread( void *param );
void SetStatus( const wchar_t *newStatus );
void Callback();
ThreadID *cloudThread;
wchar_t *statusText;
Nullsoft::Utility::LockGuard statusGuard;
HANDLE cloudEvent, cloudDone;
TimerHandle cloudTimerEvent;
static int CloudThreadPoolFunc( HANDLE handle, void *user_data, intptr_t param );
};
#endif

View File

@ -0,0 +1,12 @@
major components:
Cloud
-----
The cloud is the object which is responsible for doing period updates on RSS feeds.
It automatically scans the feeds. You can manually refresh via the Refresh(string url) method

View File

@ -0,0 +1,40 @@
#include "main.h"
#include "Defaults.h"
#include <shlobj.h>
wchar_t defaultDownloadPath[MAX_PATH] = {0},
serviceUrl[1024] = {0};
__time64_t updateTime = 60 /* 1 minute */ * 60 /* 1 hour */ * 24 /* 1 day */;
int autoDownloadEpisodes = 1;
bool autoUpdate = true;
bool autoDownload = true;
bool updateOnLaunch = false;
bool needToMakePodcastsView=true;
static BOOL UtilGetSpecialFolderPath( HWND hwnd, wchar_t *path, int folder )
{
ITEMIDLIST *pidl; // Shell Item ID List ptr
IMalloc *imalloc; // Shell IMalloc interface ptr
BOOL result; // Return value
if ( SHGetSpecialFolderLocation( hwnd, folder, &pidl ) != NOERROR )
return FALSE;
result = SHGetPathFromIDList( pidl, path );
if ( SHGetMalloc( &imalloc ) == NOERROR )
{
imalloc->Free( pidl );
imalloc->Release();
}
return result;
}
void BuildDefaultDownloadPath( HWND hwnd )
{
wchar_t defaultPath[ MAX_PATH ] = L"";
if ( !UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_MYMUSIC ) )
UtilGetSpecialFolderPath( hwnd, defaultPath, CSIDL_PERSONAL );
lstrcpyn( defaultDownloadPath, defaultPath, MAX_PATH );
}

View File

@ -0,0 +1,34 @@
#ifndef NULLSOFT_DEFAULTSH
#define NULLSOFT_DEFAULTSH
#include <windows.h>
extern wchar_t defaultDownloadPath[MAX_PATH], serviceUrl[1024];
extern __time64_t updateTime;
extern int autoDownloadEpisodes;
extern bool autoUpdate;
extern bool autoDownload;
extern bool updateOnLaunch;
extern float htmlDividerPercent;
extern float channelDividerPercent;
extern int itemTitleWidth;
extern int itemDateWidth;
extern int itemMediaWidth;
extern int itemSizeWidth;
#define DOWNLOADSCHANNELWIDTHDEFAULT 200
#define DOWNLOADSITEMWIDTHDEFAULT 200
#define DOWNLOADSPROGRESSWIDTHDEFAULT 100
#define DOWNLOADSPATHWIDTHDEFAULTS 200
extern int downloadsChannelWidth;
extern int downloadsItemWidth;
extern int downloadsProgressWidth;
extern int downloadsPathWidth;
extern bool needToMakePodcastsView;
extern int currentItemSort;
extern bool itemSortAscending;
extern bool channelSortAscending;
extern int channelLastSelection;
void BuildDefaultDownloadPath(HWND);
#endif

View File

@ -0,0 +1 @@
#include "main.h"

View File

@ -0,0 +1,153 @@
#include "main.h"
#include "api__ml_wire.h"
#include "DownloadStatus.h"
#include "DownloadsDialog.h"
#include "./navigation.h"
#include <strsafe.h>
DownloadStatus downloadStatus;
using namespace Nullsoft::Utility;
DownloadStatus::Status::Status()
{
Init();
}
DownloadStatus::Status::Status( size_t _downloaded, size_t _maxSize, const wchar_t *_channel, const wchar_t *_item, const wchar_t *_path )
{
Init();
downloaded = _downloaded;
maxSize = _maxSize;
channel = _wcsdup( _channel );
item = _wcsdup( _item );
path = _wcsdup( _path );
}
const DownloadStatus::Status &DownloadStatus::Status::operator =( const DownloadStatus::Status &copy )
{
Reset();
Init();
downloaded = copy.downloaded;
maxSize = copy.maxSize;
channel = _wcsdup( copy.channel );
item = _wcsdup( copy.item );
path = _wcsdup( copy.path );
killswitch = copy.killswitch;
return *this;
}
DownloadStatus::Status::~Status()
{
Reset();
}
void DownloadStatus::Status::Init()
{
downloaded = 0;
maxSize = 0;
killswitch = 0;
channel = 0;
item = 0;
path = 0;
}
void DownloadStatus::Status::Reset()
{
if ( channel )
{
free( channel );
channel = 0;
}
if ( item )
{
free( item );
item = 0;
}
if ( path )
{
free( path );
path = 0;
}
}
void DownloadStatus::AddDownloadThread(DownloadToken token, const wchar_t *channel, const wchar_t *item, const wchar_t *path)
{
{
AutoLock lock(statusLock);
downloads[token] = Status(0,0,channel,item,path);
DownloadsUpdated(downloads[token],token);
}
Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
}
void DownloadStatus::DownloadThreadDone(DownloadToken token)
{
{
AutoLock lock(statusLock);
downloads.erase(token);
}
Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
}
bool DownloadStatus::UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize)
{
AutoLock lock(statusLock);
downloads[token].downloaded = downloaded;
downloads[token].maxSize = maxSize;
return !!downloads[token].killswitch;
}
bool DownloadStatus::CurrentlyDownloading()
{
AutoLock lock(statusLock);
return !downloads.empty();
}
void DownloadStatus::GetStatusString( wchar_t *status, size_t len )
{
AutoLock lock( statusLock );
Downloads::iterator itr;
size_t bytesDownloaded = 0, bytesTotal = 0, numDownloads = 0;
bool unknownTotal = false;
for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
{
Status &dlstatus = itr->second;
if ( dlstatus.maxSize )
{
numDownloads++;
bytesDownloaded += dlstatus.downloaded;
bytesTotal += dlstatus.maxSize;
}
else // don't have a max size
{
if ( dlstatus.downloaded ) // if we've downloaded some then we just don't know the total
{
unknownTotal = true;
numDownloads++;
bytesDownloaded += dlstatus.downloaded;
}
}
}
if ( 0 == numDownloads )
{
status[ 0 ] = L'\0';
}
else
{
if ( unknownTotal || bytesTotal == 0 )
StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_KB_COMPLETE ), numDownloads, bytesDownloaded / 1024);
else
StringCchPrintf( status, len, WASABI_API_LNGSTRINGW( IDS_DOWNLOADING_KB_PROGRESS ), numDownloads, bytesDownloaded / 1024, bytesTotal / 1024, MulDiv( (int)bytesDownloaded, 100, (int)bytesTotal ) );
}
}

View File

@ -0,0 +1,41 @@
#ifndef NULLSOFT_DOWNLOADSTATUSH
#define NULLSOFT_DOWNLOADSTATUSH
#include "../nu/AutoLock.h"
#include <map>
class DownloadStatus
{
public:
class Status
{
public:
Status();
~Status();
const Status &operator =(const Status &copy);
Status(size_t _downloaded, size_t _maxSize, const wchar_t *channel, const wchar_t *item, const wchar_t *path);
size_t downloaded, maxSize;
int killswitch;
wchar_t *channel;
wchar_t *item;
wchar_t *path;
private:
void Init();
void Reset();
};
void AddDownloadThread(DownloadToken token, const wchar_t *channel, const wchar_t *item, const wchar_t *path);
void DownloadThreadDone(DownloadToken token);
bool UpdateStatus(DownloadToken token, size_t downloaded, size_t maxSize);
bool CurrentlyDownloading();
void GetStatusString(wchar_t *status, size_t len);
typedef std::map<DownloadToken, Status> Downloads;
Downloads downloads;
Nullsoft::Utility::LockGuard statusLock;
};
extern DownloadStatus downloadStatus;
#endif

View File

@ -0,0 +1,222 @@
#include "Main.h"
#pragma warning(disable:4786)
#include "DownloadThread.h"
#include "api__ml_wire.h"
#include "api/service/waServiceFactory.h"
#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
#include "errors.h"
#include <strsafe.h>
extern int winampVersion;
#define USER_AGENT_SIZE (10 /*User-Agent*/ + 2 /*: */ + 6 /*Winamp*/ + 1 /*/*/ + 1 /*5*/ + 3/*.21*/ + 1 /*Null*/)
void SetUserAgent( api_httpreceiver *http )
{
char user_agent[ USER_AGENT_SIZE ] = { 0 };
int bigVer = ( ( winampVersion & 0x0000FF00 ) >> 12 );
int smallVer = ( ( winampVersion & 0x000000FF ) );
StringCchPrintfA( user_agent, USER_AGENT_SIZE, "User-Agent: Winamp/%01x.%02x", bigVer, smallVer );
http->addheader( user_agent );
}
#define HTTP_BUFFER_SIZE 32768
static int FeedXMLHTTP( api_httpreceiver *http, obj_xml *parser, bool *noData )
{
char downloadedData[ HTTP_BUFFER_SIZE ] = { 0 };
int xmlResult = API_XML_SUCCESS;
int downloadSize = http->get_bytes( downloadedData, HTTP_BUFFER_SIZE );
if ( downloadSize )
{
xmlResult = parser->xmlreader_feed( (void *)downloadedData, downloadSize );
*noData = false;
}
else
*noData = true;
return xmlResult;
}
DownloadThread::DownloadThread() : parser( 0 ), parserFactory( 0 )
{
parserFactory = plugin.service->service_getServiceByGuid( obj_xmlGUID );
if ( parserFactory )
parser = (obj_xml *)parserFactory->getInterface();
if ( parser )
{
parser->xmlreader_setCaseSensitive();
parser->xmlreader_registerCallback( L"*", &xmlDOM );
parser->xmlreader_open();
}
}
DownloadThread::~DownloadThread()
{
if ( parser )
{
parser->xmlreader_unregisterCallback( &xmlDOM );
parser->xmlreader_close();
}
if ( parserFactory && parser )
parserFactory->releaseInterface( parser );
parserFactory = 0;
parser = 0;
}
void URLToFileName( wchar_t *url )
{
while ( url && *url != 0 )
{
switch ( *url )
{
case ':':
case '/':
case '\\':
case '*':
case '?':
case '"':
case '<':
case '>':
case '|':
*url = '_';
}
url++;
}
}
#define FILE_BUFFER_SIZE 32768
void DownloadThread::DownloadFile( const wchar_t *fileName )
{
if ( !parser )
return; // no sense in continuing if there's no parser available
HANDLE file = CreateFile( fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
if ( file == INVALID_HANDLE_VALUE )
return;
while ( true )
{
char data[ FILE_BUFFER_SIZE ] = { 0 };
DWORD bytesRead = 0;
if ( ReadFile( file, data, FILE_BUFFER_SIZE, &bytesRead, NULL ) && bytesRead )
{
parser->xmlreader_feed( (void *)data, bytesRead );
}
else
break;
}
CloseHandle( file );
parser->xmlreader_feed( 0, 0 );
ReadNodes( fileName );
}
#ifdef _DEBUG
#include <iostream>
void ShowAllHeaders( api_httpreceiver *http )
{
std::cout << "--------------------" << std::endl;
const char *blah = http->getallheaders();
while ( blah && *blah )
{
std::cout << blah << std::endl;
blah += strlen( blah ) + 1;
}
}
#else
#define ShowAllHeaders(x)
#endif
int RunXMLDownload( api_httpreceiver *http, obj_xml *parser )
{
int ret;
bool noData;
do
{
Sleep( 50 );
ret = http->run();
if ( FeedXMLHTTP( http, parser, &noData ) != API_XML_SUCCESS )
return DOWNLOAD_ERROR_PARSING_XML;
} while ( ret == HTTPRECEIVER_RUN_OK );
// finish off the data
do
{
if ( FeedXMLHTTP( http, parser, &noData ) != API_XML_SUCCESS )
return DOWNLOAD_ERROR_PARSING_XML;
} while ( !noData );
parser->xmlreader_feed( 0, 0 );
if ( ret != HTTPRECEIVER_RUN_ERROR )
return DOWNLOAD_SUCCESS;
else
return DOWNLOAD_CONNECTIONRESET;
}
int DownloadThread::DownloadURL( const wchar_t *url )
{
if ( !parser )
return DOWNLOAD_NOPARSER; // no sense in continuing if there's no parser available
api_httpreceiver *http = 0;
waServiceFactory *sf = plugin.service->service_getServiceByGuid( httpreceiverGUID );
if ( sf )
http = (api_httpreceiver *)sf->getInterface();
if ( !http )
return DOWNLOAD_NOHTTP;
http->AllowCompression();
http->open( API_DNS_AUTODNS, HTTP_BUFFER_SIZE, mediaLibrary.GetProxy() );
SetUserAgent( http );
http->connect( AutoChar( url ) );
int ret;
do
{
Sleep( 50 );
ret = http->run();
if ( ret == -1 ) // connection failed
break;
// ---- check our reply code ----
int status = http->get_status();
switch ( status )
{
case HTTPRECEIVER_STATUS_CONNECTING:
case HTTPRECEIVER_STATUS_READING_HEADERS:
break;
case HTTPRECEIVER_STATUS_READING_CONTENT:
{
ShowAllHeaders( http ); // benski> don't cut, only enabled in debug mode
int downloadError;
downloadError = RunXMLDownload( http, parser );
if ( downloadError == DOWNLOAD_SUCCESS )
ReadNodes( url );
sf->releaseInterface( http );
return downloadError;
}
break;
case HTTPRECEIVER_STATUS_ERROR:
default:
sf->releaseInterface( http );
return DOWNLOAD_404;
}
} while ( ret == HTTPRECEIVER_RUN_OK );
const char *er = http->geterrorstr();
sf->releaseInterface( http );
return DOWNLOAD_404;
}

View File

@ -0,0 +1,28 @@
#ifndef NULLSOFT_DOWNLOADTHREADH
#define NULLSOFT_DOWNLOADTHREADH
#include "../xml/obj_xml.h"
#include "../xml/XMLDOM.h"
#include "../nu/Alias.h"
#include "api__ml_wire.h"
#include <api/service/waServiceFactory.h>
class DownloadThread
{
public:
DownloadThread();
virtual ~DownloadThread();
virtual void ReadNodes(const wchar_t *url) = 0;
int DownloadURL(const wchar_t *url);
void DownloadFile(const wchar_t *fileName);
protected:
XMLDOM xmlDOM;
private:
obj_xml *parser;
waServiceFactory *parserFactory;
};
#endif

View File

@ -0,0 +1,121 @@
#include "main.h"
#include "Downloaded.h"
DownloadList downloadedFiles;
using namespace Nullsoft::Utility;
Nullsoft::Utility::LockGuard downloadedLock;
DownloadedFile::DownloadedFile()
{
Init();
}
DownloadedFile::DownloadedFile(const wchar_t *_url, const wchar_t *_path, const wchar_t *_channel, const wchar_t *_item, __time64_t publishDate)
{
Init();
this->publishDate = publishDate;
SetChannel( _channel );
SetItem( _item );
SetPath( _path );
SetURL( _url );
}
DownloadedFile::DownloadedFile( const DownloadedFile &copy )
{
Init();
operator =( copy );
}
DownloadedFile::~DownloadedFile()
{
Reset();
}
void DownloadedFile::Init()
{
url = 0;
path = 0;
channel = 0;
item = 0;
bytesDownloaded = 0;
totalSize = 0;
publishDate = 0;
}
void DownloadedFile::Reset()
{
if ( url )
{
free( url );
url = 0;
}
if ( path )
{
free( path );
path = 0;
}
if ( channel )
{
free( channel );
channel = 0;
}
if ( item )
{
free( item );
item = 0;
}
}
void DownloadedFile::SetPath( const wchar_t *_path )
{
if ( path )
free( path );
path = _wcsdup( _path );
}
void DownloadedFile::SetURL( const wchar_t *_url )
{
if ( url )
free( url );
url = _wcsdup( _url );
}
void DownloadedFile::SetItem( const wchar_t *_item )
{
free( item );
item = _wcsdup( _item );
}
void DownloadedFile::SetChannel( const wchar_t *_channel )
{
free( channel );
channel = _wcsdup( _channel );
}
const DownloadedFile &DownloadedFile::operator =( const DownloadedFile &copy )
{
Reset();
Init();
SetChannel( copy.channel );
SetItem( copy.item );
bytesDownloaded = copy.bytesDownloaded;
totalSize = copy.totalSize;
publishDate = copy.publishDate;
downloadDate = copy.downloadDate;
SetPath( copy.path );
SetURL( copy.url );
return *this;
}

View File

@ -0,0 +1,79 @@
#ifndef NULLSOFT_DOWNLOADEDH
#define NULLSOFT_DOWNLOADEDH
#include "../nu/AutoLock.h"
#include "../nu/AutoCharFn.h"
#include "..\..\General\gen_ml/ml.h"
#include <vector>
#include "../nu/MediaLibraryInterface.h"
class DownloadedFile
{
public:
DownloadedFile();
DownloadedFile( const wchar_t *_url, const wchar_t *_path, const wchar_t *_channel, const wchar_t *_item, __time64_t publishDate );
DownloadedFile( const DownloadedFile &copy );
~DownloadedFile();
const DownloadedFile &operator =( const DownloadedFile &copy );
void SetPath( const wchar_t *_path );
void SetURL( const wchar_t *_url );
void SetItem( const wchar_t *_item );
void SetChannel( const wchar_t *_channel );
size_t bytesDownloaded = 0;
size_t totalSize = 0;
__time64_t publishDate;
__time64_t downloadDate = 0;
wchar_t *path = 0;
wchar_t *url = 0;
wchar_t *channel = 0;
wchar_t *item = 0;
private:
void Init();
void Reset();
};
class DownloadList
{
public:
typedef std::vector<DownloadedFile> DownloadedFileList;
typedef DownloadedFileList::iterator iterator;
typedef DownloadedFileList::const_iterator const_iterator;
operator Nullsoft::Utility::LockGuard &() { return downloadedLock; }
void Remove( size_t index ) { downloadList.erase( downloadList.begin() + index ); }
bool RemoveAndDelete( int index )
{
SendMessage( mediaLibrary.library, WM_ML_IPC, (WPARAM)downloadList[ index ].path, ML_IPC_DB_REMOVEITEMW );
if ( !DeleteFile( downloadList[ index ].path ) && GetLastError() != ERROR_FILE_NOT_FOUND )
return false;
downloadList.erase( downloadList.begin() + index );
return true;
}
DownloadedFileList downloadList;
Nullsoft::Utility::LockGuard downloadedLock;
iterator begin() { return downloadList.begin(); }
iterator end() { return downloadList.end(); }
};
extern DownloadList downloadedFiles;
extern int downloadsItemSort;
extern bool downloadsSortAscending;
void CleanupDownloads();
__time64_t filetime(const wchar_t *file);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
#ifndef NULLSOFT_DOWNLOADSDIALOGH
#define NULLSOFT_DOWNLOADSDIALOGH
#include "DownloadStatus.h"
class OmService;
HWND CALLBACK DownloadDialog_Create(HWND hParent, OmService *service);
void DownloadsUpdated();
void DownloadsUpdated( DownloadToken token, const DownloadedFile *f );
void DownloadsUpdated( const DownloadStatus::Status &s, DownloadToken token );
#endif

View File

@ -0,0 +1,204 @@
#include "Main.h"
#include "DownloadsParse.h"
#include "Downloaded.h"
#include "Defaults.h"
#include "ParseUtil.h"
#include <wchar.h>
#include <locale.h>
static __time64_t filetime( const wchar_t *file )
{
if ( !file || !*file )
return 0;
WIN32_FIND_DATA f = { 0 };
HANDLE h = FindFirstFile( file, &f );
if ( h == INVALID_HANDLE_VALUE )
return 0;
FindClose( h );
SYSTEMTIME s = { 0 };
FileTimeToSystemTime( &f.ftCreationTime, &s );
tm t = { 0 };
t.tm_year = s.wYear - 1900;
t.tm_mon = s.wMonth - 1;
t.tm_mday = s.wDay;
t.tm_hour = s.wHour;
t.tm_min = s.wMinute;
t.tm_sec = s.wMinute;
return _mktime64( &t );
}
static void ReadDownload( const XMLNode *item, bool addToLib = false )
{
DownloadedFile newDownloaded;
const wchar_t *channel = GetContent( item, L"channel" );
newDownloaded.SetChannel( channel );
const wchar_t *item_str = GetContent( item, L"item" );
newDownloaded.SetItem( item_str );
const wchar_t *url = GetContent( item, L"url" );
newDownloaded.SetURL( url );
const wchar_t *path = GetContent( item, L"path" );
newDownloaded.SetPath( path );
const wchar_t *publishDate = GetContent( item, L"publishDate" );
if ( publishDate && publishDate[ 0 ] )
newDownloaded.publishDate = _wtoi( publishDate );
else
newDownloaded.publishDate = filetime( newDownloaded.path );
if ( addToLib )
addToLibrary( newDownloaded );
downloadedFiles.downloadList.push_back( newDownloaded );
}
static void ReadPreferences( const XMLNode *item )
{
const XMLNode *curNode;
curNode = item->Get(L"download");
if ( curNode )
{
const wchar_t *prop = curNode->GetProperty( L"downloadpath" );
if ( prop )
lstrcpyn( defaultDownloadPath, prop, MAX_PATH );
autoDownload = PropertyIsTrue( curNode, L"autodownload" );
prop = curNode->GetProperty( L"autoDownloadEpisodes" );
if ( prop )
autoDownloadEpisodes = _wtoi( prop );
needToMakePodcastsView = PropertyIsTrue( curNode, L"needToMakePodcastsView" );
}
curNode = item->Get(L"update");
if ( curNode )
{
const wchar_t *prop = curNode->GetProperty( L"updatetime" );
if ( prop )
updateTime = _wtoi64( prop );
autoUpdate = PropertyIsTrue( curNode, L"autoupdate" );
updateOnLaunch = PropertyIsTrue( curNode, L"updateonlaunch" );
}
curNode = item->Get(L"subscriptions");
if ( curNode )
{
_locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
const wchar_t *prop = curNode->GetProperty( L"htmldivider" );
if ( prop )
htmlDividerPercent = (float)_wtof_l( prop, C_locale );
prop = curNode->GetProperty( L"channeldivider" );
if ( prop && prop[ 0 ] )
channelDividerPercent = (float)_wtof_l( prop, C_locale );
prop = curNode->GetProperty( L"itemtitlewidth" );
if ( prop && prop[ 0 ] )
itemTitleWidth = _wtoi( prop );
prop = curNode->GetProperty( L"itemdatewidth" );
if ( prop && prop[ 0 ] )
itemDateWidth = _wtoi( prop );
prop = curNode->GetProperty( L"itemmediawidth" );
if ( prop && prop[ 0 ] )
itemMediaWidth = _wtoi( prop );
prop = curNode->GetProperty( L"itemsizewidth" );
if ( prop && prop[ 0 ] )
itemSizeWidth = _wtoi( prop );
prop = curNode->GetProperty( L"currentitemsort" );
if ( prop && prop[ 0 ] )
currentItemSort = _wtoi( prop );
itemSortAscending = !PropertyIsFalse( curNode, L"itemsortascending" );
channelSortAscending = !PropertyIsFalse( curNode, L"channelsortascending" );
prop = curNode->GetProperty( L"channelLastSelection" );
if ( prop && prop[ 0 ] )
channelLastSelection = _wtoi( prop );
}
curNode = item->Get(L"downloadsView");
if ( curNode )
{
const wchar_t *prop = curNode->GetProperty( L"downloadsChannelWidth" );
if ( prop && prop[ 0 ] )
downloadsChannelWidth = _wtoi( prop );
if ( downloadsChannelWidth <= 0 )
downloadsChannelWidth = DOWNLOADSCHANNELWIDTHDEFAULT;
prop = curNode->GetProperty( L"downloadsItemWidth" );
if ( prop && prop[ 0 ] )
downloadsItemWidth = _wtoi( prop );
if ( downloadsItemWidth <= 0 )
downloadsItemWidth = DOWNLOADSITEMWIDTHDEFAULT;
prop = curNode->GetProperty( L"downloadsProgressWidth" );
if ( prop && prop[ 0 ] )
downloadsProgressWidth = _wtoi( prop );
if ( downloadsProgressWidth <= 0 )
downloadsProgressWidth = DOWNLOADSPROGRESSWIDTHDEFAULT;
prop = curNode->GetProperty( L"downloadsPathWidth" );
if ( prop && prop[ 0 ] )
downloadsPathWidth = _wtoi( prop );
if ( downloadsPathWidth <= 0 )
downloadsPathWidth = DOWNLOADSPATHWIDTHDEFAULTS;
prop = curNode->GetProperty( L"downloadsItemSort" );
if ( prop && prop[ 0 ] )
downloadsItemSort = _wtoi( prop );
downloadsSortAscending = !PropertyIsFalse( curNode, L"downloadsSortAscending" );
}
curNode = item->Get(L"service");
if ( curNode )
{
const wchar_t *prop = curNode->GetProperty( L"url" );
if ( prop )
lstrcpyn( serviceUrl, prop, MAX_PATH );
}
}
void DownloadsParse::ReadNodes( const wchar_t *url )
{
XMLNode::NodeList::const_iterator itr;
const XMLNode *curNode = xmlDOM.GetRoot();
curNode = curNode->Get( L"winamp:preferences" );
if ( curNode )
{
int version = 1;
const wchar_t *prop = curNode->GetProperty( L"version" );
if ( prop && prop[ 0 ] )
version = _wtoi( prop );
ReadPreferences( curNode );
curNode = curNode->Get( L"downloads" );
if ( curNode )
{
const XMLNode::NodeList *downloadsList = curNode->GetList( L"download" );
if ( downloadsList )
{
for ( itr = downloadsList->begin(); itr != downloadsList->end(); itr++ )
ReadDownload( *itr, version < 2 );
}
}
}
}

View File

@ -0,0 +1,13 @@
#ifndef NULLSOFT_DOWNLOADSPARSEH
#define NULLSOFT_DOWNLOADSPARSEH
#include "DownloadThread.h"
class DownloadsParse : public DownloadThread
{
public:
virtual void ReadNodes(const wchar_t *url);
};
#endif

View File

@ -0,0 +1,85 @@
#include "main.h"
#include "./externalCOM.h"
#include "./util.h"
#include "./rssCOM.h"
#define DISPTABLE_CLASS ExternalCOM
DISPTABLE_BEGIN()
DISPENTRY_ADD(DISPATCH_PODCAST, L"Podcast", OnPodcast)
DISPTABLE_END
#undef DISPTABLE_CLASS
ExternalCOM::ExternalCOM()
{}
ExternalCOM::~ExternalCOM()
{}
HRESULT ExternalCOM::CreateInstance(ExternalCOM **instance)
{
if (NULL == instance) return E_POINTER;
*instance = new ExternalCOM();
if (NULL == *instance) return E_OUTOFMEMORY;
return S_OK;
}
STDMETHODIMP_( ULONG ) ExternalCOM::AddRef( void )
{
return _ref.fetch_add( 1 );
}
STDMETHODIMP_( ULONG ) ExternalCOM::Release( void )
{
if ( 0 == _ref.load() )
return _ref.load();
LONG r = _ref.fetch_sub( 1 );
if ( 0 == r )
delete( this );
return r;
}
STDMETHODIMP ExternalCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
{
if (NULL == ppvObject) return E_POINTER;
if (IsEqualIID(riid, IID_IDispatch))
*ppvObject = static_cast<IDispatch*>(this);
else if (IsEqualIID(riid, IID_IUnknown))
*ppvObject = static_cast<IUnknown*>(this);
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
HRESULT ExternalCOM::OnPodcast(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
{
if (NULL != pvarResult)
{
VariantInit(pvarResult);
RssCOM *rss;
if (SUCCEEDED(RssCOM::CreateInstance(&rss)))
{
V_VT(pvarResult) = VT_DISPATCH;
V_DISPATCH(pvarResult) = rss;
}
else
{
V_VT(pvarResult) = VT_NULL;
}
}
return S_OK;
}

View File

@ -0,0 +1,40 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_EXTERNAL_HEADER
#define NULLSOFT_PODCAST_PLUGIN_EXTERNAL_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
#include <atomic>
#include "../nu/dispatchTable.h"
class ExternalCOM : public IDispatch
{
public:
typedef enum
{
DISPATCH_PODCAST = 777,
} DispatchCodes;
protected:
ExternalCOM();
~ExternalCOM();
public:
static HRESULT CreateInstance(ExternalCOM **instance);
/* IUnknown*/
STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
protected:
DISPTABLE_INCLUDE();
DISPHANDLER_REGISTER(OnPodcast);
std::atomic<std::size_t> _ref = 1;
};
#endif //NULLSOFT_PODCAST_PLUGIN_EXTERNAL_HEADER

View File

@ -0,0 +1,64 @@
#include "api__ml_wire.h"
#include "Factory.h"
#include "Wire.h"
static const char serviceName[] = "Podcasts";
FOURCC PodcastsFactory::GetServiceType()
{
return WaSvc::UNIQUE;
}
const char *PodcastsFactory::GetServiceName()
{
return serviceName;
}
GUID PodcastsFactory::GetGUID()
{
return api_podcastsGUID;
}
void *PodcastsFactory::GetInterface( int global_lock )
{
// if (global_lock)
// plugin.service->service_lock(this, (void *)ifc);
return &channels;
}
int PodcastsFactory::SupportNonLockingInterface()
{
return 1;
}
int PodcastsFactory::ReleaseInterface( void *ifc )
{
//plugin.service->service_unlock(ifc);
return 1;
}
const char *PodcastsFactory::GetTestString()
{
return 0;
}
int PodcastsFactory::ServiceNotify( int msg, int param1, int param2 )
{
return 1;
}
#ifdef CBCLASS
#undef CBCLASS
#endif
#define CBCLASS PodcastsFactory
START_DISPATCH;
CB( WASERVICEFACTORY_GETSERVICETYPE, GetServiceType )
CB( WASERVICEFACTORY_GETSERVICENAME, GetServiceName )
CB( WASERVICEFACTORY_GETGUID, GetGUID )
CB( WASERVICEFACTORY_GETINTERFACE, GetInterface )
CB( WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface )
CB( WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface )
CB( WASERVICEFACTORY_GETTESTSTRING, GetTestString )
CB( WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify )
END_DISPATCH;

View File

@ -0,0 +1,22 @@
#ifndef NULLSOFT_ML_WIRE_FACTORY_H
#define NULLSOFT_ML_WIRE_FACTORY_H
#include "api__ml_wire.h"
#include <api/service/waservicefactory.h>
#include <api/service/services.h>
class PodcastsFactory : public waServiceFactory
{
public:
FOURCC GetServiceType();
const char *GetServiceName();
GUID GetGUID();
void *GetInterface(int global_lock);
int SupportNonLockingInterface();
int ReleaseInterface(void *ifc);
const char *GetTestString();
int ServiceNotify(int msg, int param1, int param2);
protected:
RECVS_DISPATCH;
};
#endif

View File

@ -0,0 +1,38 @@
#include "Main.h"
#include "FeedParse.h"
#include "RFCDate.h"
#include "RSSParse.h"
#include "AtomParse.h"
#ifdef DEBUG
#include <iostream>
static void DisplayNodes(XMLNode &node)
{
XMLNode::NodeMap::iterator nodeItr;
for (nodeItr = node.nodes.begin();nodeItr != node.nodes.end(); nodeItr++)
{
for (XMLNode::NodeList::iterator itr = nodeItr->second.begin(); itr != nodeItr->second.end(); itr++)
{
std::wcerr << L"<" << nodeItr->first << L">" << std::endl;
DisplayNodes(**itr);
std::wcerr << L"</" << nodeItr->first << L">" << std::endl;
}
}
}
#endif
void FeedParse::ReadNodes(const wchar_t *url)
{
const XMLNode *curNode = xmlDOM.GetRoot();
curNode = curNode->Get(L"rss");
if (curNode)
{
ReadRSS(curNode, sync, loadingOwnFeed, url);
return ;
}
}

View File

@ -0,0 +1,24 @@
#ifndef NULLSOFT_FEEDPARSEH
#define NULLSOFT_FEEDPARSEH
#include "DownloadThread.h"
#include "ChannelSync.h"
class FeedParse : public DownloadThread
{
public:
FeedParse(ChannelSync *_sync, bool doWinampSpecificTags = false)
: sync(_sync), loadingOwnFeed(doWinampSpecificTags)
{}
~FeedParse()
{
sync = 0;
}
virtual void ReadNodes(const wchar_t *url);
private:
ChannelSync *sync;
bool loadingOwnFeed;
};
#endif

View File

@ -0,0 +1,37 @@
#include "main.h"
#include "FeedUtil.h"
#include "ChannelCheck.h"
#include "FeedParse.h"
#include "errors.h"
#include "./defaults.h"
int DownloadFeedInformation(Channel &newFeed)
{
ChannelCheck check;
FeedParse downloader(&check, false);
int ret = downloader.DownloadURL(newFeed.url);
if (ret != DOWNLOAD_SUCCESS)
return ret;
if (!check.channel.title || !check.channel.title[0])
return DOWNLOAD_NOTRSS;
newFeed.SetTitle(check.channel.title);
if (check.channel.ttl)
{
newFeed.updateTime = check.channel.ttl * 60;
newFeed.autoUpdate = true;
}
else
{
newFeed.updateTime = ::updateTime;
newFeed.autoUpdate = ::autoUpdate;
}
if (check.channel.url && check.channel.url[0])
newFeed.SetURL(check.channel.url);
return DOWNLOAD_SUCCESS;
}

View File

@ -0,0 +1,8 @@
#ifndef NULLSOFT_FEEDUTILH
#define NULLSOFT_FEEDUTILH
#include "Feeds.h"
int DownloadFeedInformation(Channel &channel);
#endif

View File

@ -0,0 +1,298 @@
#include "main.h"
#include "Feeds.h"
#include "./util.h"
#include "./defaults.h"
#include <algorithm>
#include <shlwapi.h>
#include "BackgroundDownloader.h"
#include <strsafe.h>
static bool operator == (const RSS::Item &a, const RSS::Item &b)
{
if(a.guid && a.guid[0] && b.guid && b.guid[0])
return !wcscmp(a.guid, b.guid);
if(a.publishDate && b.publishDate)
return a.publishDate == b.publishDate;
if (a.url && a.url[0] && b.url && b.url[0])
return !wcscmp(a.url, b.url);
return a.url == b.url;
}
Channel::Channel()
{
Init();
}
Channel::Channel(const Channel &copy)
{
Init();
operator =(copy);
}
const Channel &Channel::operator =(const Channel &copy)
{
Reset();
Init();
url = _wcsdup(copy.url);
title = _wcsdup(copy.title);
link = _wcsdup(copy.link);
description = _wcsdup(copy.description);
ttl=copy.ttl;
updateTime=copy.updateTime;
lastUpdate=copy.lastUpdate;
autoDownloadEpisodes=copy.autoDownloadEpisodes;
autoDownload=copy.autoDownload;
autoUpdate=copy.autoUpdate;
useDefaultUpdate=copy.useDefaultUpdate;
needsRefresh=copy.needsRefresh;
items=copy.items;
return *this;
}
Channel::~Channel()
{
Reset();
}
void Channel::Init()
{
url =0;
title = 0;
link = 0;
description = 0;
ttl=0;
lastUpdate=0;
autoUpdate = ::autoUpdate;
updateTime = ::updateTime;
autoDownload = ::autoDownload;
autoDownloadEpisodes = ::autoDownloadEpisodes;
useDefaultUpdate=true;
needsRefresh=false;
}
void Channel::Reset()
{
free(url);
free(title);
free(link);
free(description);
}
void Channel::UpdateFrom(const Channel &copy)
{
if (copy.url && copy.url[0])
SetURL(copy.url);
SetTitle(copy.title);
SetLink(copy.link);
SetDescription(copy.description);
if (copy.ttl)
ttl=copy.ttl;
ItemList::const_iterator itr;
for (itr=copy.items.begin();itr!=copy.items.end();itr++)
{
const RSS::Item &b = *itr;
if ( b.url && b.url[0] )
{
((RSS::Item*)&b)->downloaded = IsPodcastDownloaded(b.url);
}
}
// update to the latest default setting
if (useDefaultUpdate)
{
autoUpdate = ::autoUpdate;
updateTime = ::updateTime;
autoDownload = ::autoDownload;
autoDownloadEpisodes = ::autoDownloadEpisodes;
}
items.clear(); // benski> added for 5.23
for (itr=copy.items.begin();itr!=copy.items.end();itr++)
{
items.insert(items.begin(), *itr);
}
if(autoDownload)
{
SortByDate();
size_t idx = items.size();
if (idx)
{
int episodeCount = 0;
do
{
idx--;
const RSS::Item &b = items[idx];
if(b.url && b.url[0])
{
episodeCount++;
if (!b.downloaded)
{
WCHAR szPath[MAX_PATH *2] = {0};
if (SUCCEEDED(((RSS::Item*)&b)->GetDownloadFileName(title, szPath, ARRAYSIZE(szPath), TRUE)))
{
wchar_t* url = urlencode(b.url);
downloader.Download(url, szPath, title, b.itemName, b.publishDate);
((RSS::Item*)&b)->downloaded = true;
free(url);
}
}
}
} while (episodeCount<autoDownloadEpisodes && idx);
}
}
}
bool Channel::operator == (const Channel &compare)
{
// changed from basing on the title as this allows for podcasts
// with the same name to still work instead of being mangled as
// was able to happen when this based things on the title value
if (!compare.url || !compare.url[0])
return false;
return !wcscmp(url, compare.url);
}
bool TitleMediaSort(const RSS::Item &item1, const RSS::Item &item2)
{
return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item1.itemName, -1, item2.itemName, -1));
}
void Channel::SortByTitle()
{
std::sort(items.begin(), items.end(), TitleMediaSort);
}
static bool ItemMediaSort(const RSS::Item &item1, const RSS::Item &item2)
{
if (!item1.url || !item1.url[0])
return false;
if (!item2.url || !item2.url[0])
return true;
if (!item2.listened)
return false;
if (item1.listened)
return false;
return true;
}
bool ParseDuration(const wchar_t *duration, int *out_hours, int *out_minutes, int *out_seconds);
static bool ItemMediaTimeSort(const RSS::Item &item1, const RSS::Item &item2)
{
if (!item1.duration || !item1.duration[0])
return false;
if (!item2.duration || !item2.duration[0])
return true;
int h1, h2, m1, m2, s1, s2;
if (!ParseDuration(item1.duration, &h1, &m1, &s1))
return false;
if (!ParseDuration(item2.duration, &h2, &m2, &s2))
return true;
if (h1 < h2)
return true;
else if (h1 > h2)
return false;
if (m1 < m2)
return true;
else if (m1 > m2)
return false;
if (s1 < s2)
return true;
else
return false;
}
static bool ItemMediaSizeSort(const RSS::Item &item1, const RSS::Item &item2)
{
if (!item1.size)
return false;
if (!item2.size)
return true;
return item1.size < item2.size;
}
void Channel::SortByMedia()
{
std::sort(items.begin(), items.end(), ItemMediaSort);
}
void Channel::SortByMediaTime()
{
std::sort(items.begin(), items.end(), ItemMediaTimeSort);
}
void Channel::SortByMediaSize()
{
std::sort(items.begin(), items.end(), ItemMediaSizeSort);
}
bool ItemDateSort(const RSS::Item &item1, const RSS::Item &item2)
{
return (item1.publishDate < item2.publishDate);
}
void Channel::SortByDate()
{
std::sort(items.begin(), items.end(), ItemDateSort);
}
int Channel::GetTitle(wchar_t *str, size_t len)
{
if (str && len)
{
str[0]=0;
if (title && title[0])
StringCchCopyW(str, len, title);
return 0;
}
return 1;
}
void Channel::SetURL(const wchar_t *val)
{
free(url);
url = _wcsdup(val);
}
void Channel::SetTitle(const wchar_t *val)
{
free(title);
title = _wcsdup(val);
}
void Channel::SetLink(const wchar_t *val)
{
free(link);
link = _wcsdup(val);
}
void Channel::SetDescription(const wchar_t *val)
{
free(description);
description = _wcsdup(val);
}
#undef CBCLASS
#define CBCLASS Channel
START_DISPATCH;
CB(IFC_PODCAST_GETTITLE, GetTitle)
END_DISPATCH;
#undef CBCLASS

View File

@ -0,0 +1,50 @@
#ifndef NULLSOFT_FEEDSH
#define NULLSOFT_FEEDSH
#include "ifc_podcast.h"
#include "Item.h"
#include <vector>
class Channel : public ifc_podcast
{
public:
typedef std::vector<RSS::Item> ItemList;
Channel();
Channel(const Channel &copy);
const Channel &operator =(const Channel &copy);
~Channel();
void SortByTitle(), SortByMedia(), SortByMediaTime(), SortByDate(), SortByMediaSize();
bool operator == (const Channel &compare);
//void operator = (const Channel &copy);
void UpdateFrom(const Channel &copy);
unsigned int ttl;
__time64_t updateTime, lastUpdate;
int autoDownloadEpisodes;
bool autoUpdate;
bool useDefaultUpdate;
bool autoDownload;
bool needsRefresh;
// TODO: std::wstring downloadLocation;
ItemList items;
void SetURL(const wchar_t *val);
void SetTitle(const wchar_t *val);
void SetLink(const wchar_t *val);
void SetDescription(const wchar_t *val);
wchar_t *url, *title, *link, *description;
public: // ifc_podcast interface
int GetTitle(wchar_t *str, size_t len);
private:
void Init();
void Reset();
protected:
RECVS_DISPATCH;
};
#endif

View File

@ -0,0 +1,5 @@
#ifndef NULLSOFT_FEEDSDIALOGH
#define NULLSOFT_FEEDSDIALOGH
BOOL CALLBACK FeedsProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
#endif

View File

@ -0,0 +1,174 @@
#include "Item.h"
#include "util.h"
#include "defaults.h"
#include <shlwapi.h>
#include <strsafe.h>
void RSS::Item::Reset()
{
free(itemName);
free(url);
free(sourceUrl);
free(guid);
free(description);
free(link);
free(duration);
}
void RSS::Item::Init()
{
listened = false;
publishDate = 0;
generatedDate = true;
downloaded=false;
itemName=0;
url=0;
sourceUrl=0;
guid=0;
description=0;
link=0;
duration=0;
size=0;
}
RSS::Item::Item()
{
Init();
}
RSS::Item::~Item()
{
Reset();
}
RSS::Item::Item(const RSS::Item &copy)
{
Init();
operator =(copy);
}
const RSS::Item &RSS::Item::operator =(const RSS::Item &copy)
{
Reset();
Init();
listened=copy.listened;
publishDate = copy.publishDate;
generatedDate = copy.generatedDate;
downloaded=copy.downloaded;
itemName=_wcsdup(copy.itemName);
url=_wcsdup(copy.url);
sourceUrl=_wcsdup(copy.sourceUrl);
guid=_wcsdup(copy.guid);
description=_wcsdup(copy.description);
link=_wcsdup(copy.link);
duration=wcsdup(copy.duration);
size = copy.size;
return *this;
}
HRESULT RSS::Item::GetDownloadFileName(const wchar_t *channelName, wchar_t *buffer, int bufferMax, BOOL fValidatePath) const
{
if (NULL == buffer || NULL == channelName) return E_INVALIDARG;
buffer[0] = L'\0';
WCHAR szBuffer[MAX_PATH] = {0};
if (FAILED(StringCchCopyN(szBuffer, ARRAYSIZE(szBuffer), channelName, 100)))
return E_UNEXPECTED;
Plugin_CleanDirectory(szBuffer);
Plugin_ReplaceBadPathChars(szBuffer);
if (L'\0' == *szBuffer)
StringCchCopy(szBuffer, ARRAYSIZE(szBuffer), L"UnknownChannel");
if (FALSE == PathCombine(buffer, defaultDownloadPath, szBuffer))
return E_FAIL;
if (FALSE != fValidatePath && FAILED(Plugin_EnsurePathExist(buffer)))
return E_FAIL;
LPWSTR cursor = szBuffer;
size_t remaining = ARRAYSIZE(szBuffer);
tm* time = _localtime64(&publishDate);
if(NULL != time && publishDate > 0)
{
StringCchPrintfEx(cursor, remaining, &cursor, &remaining, STRSAFE_NULL_ON_FAILURE,
L"%04d-%02d-%02d - ", time->tm_year+1900, time->tm_mon+1, time->tm_mday);
}
LPWSTR t = cursor;
if (FAILED(StringCchCopyNEx(cursor, remaining, itemName, 100, &cursor, &remaining, 0)))
return E_UNEXPECTED;
INT offset = Plugin_CleanDirectory(t);
if (0 != offset)
{
remaining += offset;
cursor -= offset;
}
if (t == cursor)
StringCchCopyEx(cursor, remaining, L"UnknownItem", &cursor, &remaining, 0);
else
Plugin_ReplaceBadPathChars(t);
if (FAILED(Plugin_FileExtensionFromUrl(cursor, (INT)remaining, url, L".mp3")))
return E_UNEXPECTED;
if (FALSE == PathAppend(buffer, szBuffer))
return E_FAIL;
return S_OK;
}
void RSS::MutableItem::SetLink(const wchar_t *value)
{
free(link);
link = _wcsdup(value);
}
void RSS::MutableItem::SetItemName(const wchar_t *value)
{
free(itemName);
itemName = _wcsdup(value);
}
void RSS::MutableItem::SetURL(const wchar_t *value)
{
free(url);
url = _wcsdup(value);
}
void RSS::MutableItem::SetSourceURL(const wchar_t *value)
{
free(sourceUrl);
sourceUrl = _wcsdup(value);
}
void RSS::MutableItem::SetGUID(const wchar_t *value)
{
free(guid);
guid = _wcsdup(value);
}
void RSS::MutableItem::SetDescription(const wchar_t *value)
{
free(description);
description = _wcsdup(value);
}
void RSS::MutableItem::SetDuration(const wchar_t *value)
{
free(duration);
duration = _wcsdup(value);
}
void RSS::MutableItem::SetSize(const wchar_t * _size)
{
if (_size)
size = _wtoi64(_size);
else
size=0;
}

View File

@ -0,0 +1,50 @@
#pragma once
#include <bfc/platform/types.h>
#include <time.h>
#include <windows.h>
namespace RSS
{
class Item
{
public:
Item();
~Item();
Item(const Item &copy);
const Item &operator =(const Item &copy);
HRESULT GetDownloadFileName(const wchar_t *channelName, wchar_t *buffer, int bufferMax, BOOL fValidatePath) const;
bool listened;
bool downloaded;
__time64_t publishDate;
bool generatedDate;
//protected:
wchar_t *itemName;
wchar_t *url;
wchar_t *sourceUrl;
wchar_t *guid;
wchar_t *description;
wchar_t *link;
wchar_t *duration;
int64_t size;
private:
void Init();
void Reset();
};
class MutableItem : public Item
{
public:
void SetItemName(const wchar_t *value);
void SetLink(const wchar_t *value);
void SetURL(const wchar_t *value);
void SetSourceURL(const wchar_t *value);
void SetGUID(const wchar_t *value);
void SetDescription(const wchar_t *value);
void SetDuration(const wchar_t *value);
void SetSize(const wchar_t * _size);
};
}

View File

@ -0,0 +1,94 @@
#include "main.h"
#include "JSAPI2_Creator.h"
#include "JSAPI2_PodcastsAPI.h"
#include "api__ml_wire.h"
IDispatch *JSAPI2_Creator::CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info)
{
if (!_wcsicmp(name, L"Podcasts"))
return new JSAPI2::PodcastsAPI(key, info);
else
return 0;
}
int JSAPI2_Creator::PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data)
{
if (group && !_wcsicmp(group, L"podcasts"))
{
const wchar_t *title_str = AGAVE_API_JSAPI2_SECURITY->GetAssociatedName(authorization_key);
return AGAVE_API_JSAPI2_SECURITY->SecurityPrompt(parent, title_str, L"This service is trying to subscribe you to a podcast.", JSAPI2::svc_apicreator::AUTHORIZATION_FLAG_GROUP_ONLY);
}
else
return JSAPI2::svc_apicreator::AUTHORIZATION_UNDEFINED;
}
#define CBCLASS JSAPI2_Creator
START_DISPATCH;
CB(JSAPI2_SVC_APICREATOR_CREATEAPI, CreateAPI);
CB(JSAPI2_SVC_APICREATOR_PROMPTFORAUTHORIZATION, PromptForAuthorization);
END_DISPATCH;
#undef CBCLASS
static JSAPI2_Creator jsapi2_svc;
static const char serviceName[] = "Podcast Javascript Objects";
// {EE2C54DB-E609-410a-A962-573BA2F9C3AC}
static const GUID jsapi2_factory_guid =
{ 0xee2c54db, 0xe609, 0x410a, { 0xa9, 0x62, 0x57, 0x3b, 0xa2, 0xf9, 0xc3, 0xac } };
FOURCC JSAPI2Factory::GetServiceType()
{
return jsapi2_svc.getServiceType();
}
const char *JSAPI2Factory::GetServiceName()
{
return serviceName;
}
GUID JSAPI2Factory::GetGUID()
{
return jsapi2_factory_guid;
}
void *JSAPI2Factory::GetInterface(int global_lock)
{
// if (global_lock)
// plugin.service->service_lock(this, (void *)ifc);
return &jsapi2_svc;
}
int JSAPI2Factory::SupportNonLockingInterface()
{
return 1;
}
int JSAPI2Factory::ReleaseInterface(void *ifc)
{
//plugin.service->service_unlock(ifc);
return 1;
}
const char *JSAPI2Factory::GetTestString()
{
return 0;
}
int JSAPI2Factory::ServiceNotify(int msg, int param1, int param2)
{
return 1;
}
#define CBCLASS JSAPI2Factory
START_DISPATCH;
CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
CB(WASERVICEFACTORY_GETGUID, GetGUID)
CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
END_DISPATCH;
#undef CBCLASS

View File

@ -0,0 +1,31 @@
#pragma once
#include "../Winamp/JSAPI2_svc_apicreator.h"
#include <api/service/waservicefactory.h>
#include <api/service/services.h>
class JSAPI2Factory : public waServiceFactory
{
public:
FOURCC GetServiceType();
const char *GetServiceName();
GUID GetGUID();
void *GetInterface(int global_lock);
int SupportNonLockingInterface();
int ReleaseInterface(void *ifc);
const char *GetTestString();
int ServiceNotify(int msg, int param1, int param2);
protected:
RECVS_DISPATCH;
};
class JSAPI2_Creator : public JSAPI2::svc_apicreator
{
IDispatch *CreateAPI(const wchar_t *name, const wchar_t *key, JSAPI::ifc_info *info);
int PromptForAuthorization(HWND parent, const wchar_t *group, const wchar_t *action, const wchar_t *authorization_key, JSAPI2::api_security::AuthorizationData *data);
protected:
RECVS_DISPATCH;
};

View File

@ -0,0 +1,109 @@
#include "JSAPI2_PodcastsAPI.h"
#include "../Winamp/JSAPI.h"
#include "api__ml_wire.h"
#include "./rssCOM.h"
JSAPI2::PodcastsAPI::PodcastsAPI( const wchar_t *_key, JSAPI::ifc_info *_info )
{
info = _info;
key = _key;
}
enum
{
DISP_PODCASTS_SUBSCRIBE,
};
#define DISP_TABLE \
CHECK_ID(Subscribe, DISP_PODCASTS_SUBSCRIBE)\
#define CHECK_ID(str, id) if (wcscmp(rgszNames[i], L## #str) == 0) { rgdispid[i] = id; continue; }
HRESULT JSAPI2::PodcastsAPI::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid)
{
bool unknowns = false;
for (unsigned int i = 0;i != cNames;i++)
{
DISP_TABLE
rgdispid[i] = DISPID_UNKNOWN;
unknowns = true;
}
if (unknowns)
return DISP_E_UNKNOWNNAME;
else
return S_OK;
}
HRESULT JSAPI2::PodcastsAPI::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
return E_NOTIMPL;
}
HRESULT JSAPI2::PodcastsAPI::GetTypeInfoCount(unsigned int FAR * pctinfo)
{
return E_NOTIMPL;
}
HRESULT JSAPI2::PodcastsAPI::Subscribe(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
{
JSAPI_VERIFY_METHOD(wFlags);
JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
if (AGAVE_API_JSAPI2_SECURITY->GetActionAuthorization(L"podcasts", L"subscribe", key, info, JSAPI2::api_security::ACTION_PROMPT) == JSAPI2::api_security::ACTION_ALLOWED)
{
RssCOM::SubscribeUrl(JSAPI_PARAM(pdispparams, 1).bstrVal, pvarResult);
}
else
{
JSAPI_INIT_RESULT(pvarResult, VT_BOOL);
JSAPI_SET_RESULT(pvarResult, boolVal, VARIANT_FALSE);
}
return S_OK;
}
#undef CHECK_ID
#define CHECK_ID(str, id) case id: return str(wFlags, pdispparams, pvarResult, puArgErr);
HRESULT JSAPI2::PodcastsAPI::Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr)
{
switch (dispid)
{
DISP_TABLE
}
return DISP_E_MEMBERNOTFOUND;
}
STDMETHODIMP JSAPI2::PodcastsAPI::QueryInterface(REFIID riid, PVOID *ppvObject)
{
if (!ppvObject)
return E_POINTER;
else if (IsEqualIID(riid, IID_IDispatch))
*ppvObject = (IDispatch *)this;
else if (IsEqualIID(riid, IID_IUnknown))
*ppvObject = this;
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG JSAPI2::PodcastsAPI::AddRef(void)
{
return _refCount.fetch_add( 1 );
}
ULONG JSAPI2::PodcastsAPI::Release( void )
{
LONG lRef = _refCount.fetch_sub( 1 );
if ( lRef == 0 )
delete this;
return lRef;
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <ocidl.h>
#include <atomic>
#include "../Winamp/JSAPI_Info.h"
namespace JSAPI2
{
class PodcastsAPI : public IDispatch
{
public:
PodcastsAPI(const wchar_t *_key, JSAPI::ifc_info *info);
STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
// *** IDispatch Methods ***
STDMETHOD (GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid);
STDMETHOD (GetTypeInfo)(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo);
STDMETHOD (GetTypeInfoCount)(unsigned int FAR * pctinfo);
STDMETHOD (Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr);
private:
const wchar_t *key;
volatile std::atomic<std::size_t> _refCount = 1;
JSAPI::ifc_info *info;
STDMETHOD (Subscribe)(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr);
};
}

View File

@ -0,0 +1,100 @@
#include "main.h"
#include "api.h"
#include "../winamp/wa_ipc.h"
#include "DownloadStatus.h"
using namespace Nullsoft::Utility;
static WNDPROC wa_oldWndProc=0;
/* protocol must be all lower case */
bool ProtocolMatch(const char *file, const char *protocol)
{
size_t protSize = strlen(protocol);
for (size_t i=0;i!=protSize;i++)
{
if (!file[i]
|| tolower(file[i]) != protocol[i])
return false;
}
return true;
}
LRESULT CALLBACK LoaderProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
#if 0 // not ready to take links yet... too buggy/weird at this point ...
if (uMsg == WM_COPYDATA)
{
COPYDATASTRUCT *copyData = (COPYDATASTRUCT *)lParam;
if (copyData->dwData == IPC_ENQUEUEFILE)
{
const char *file = (const char *)copyData->lpData;
if (ProtocolMatch(file, "feed://"))
{
Channel newFeed;
newFeed.url = AutoWide((const char *)copyData->lpData);
if (DownloadFeedInformation(newFeed)==DOWNLOADRSS_SUCCESS)
{
AutoLock lock(channels);
channels.push_back(newFeed);
}
return 0;
}
else
if (ProtocolMatch(file, "http://"))
{
// nothing for now, we want to do a head request tho
JNL_HTTPGet head;
head.connect(file, 0, "HEAD");
int ret;
do
{
ret = head.run();
Sleep(50);
} while (ret != -1 && ret != 1);
if (ret!=-1)
{
char *contentType = head.getheader("Content-Type");
// if (contentType)
//MessageBoxA(NULL, contentType, contentType, MB_OK);
if (strstr(contentType, "application/rss+xml") == contentType)
{
MessageBox(NULL, L"woo!", L"woo!", MB_OK);
return 0;
}
if (strstr(contentType, "application/xml") == contentType)
{
MessageBox(NULL, L"regular xml", L"application/xml", MB_OK);
return 0;
}
if (strstr(contentType, "text/xml") == contentType)
{
MessageBox(NULL, L"regular xml", L"text/xml", MB_OK);
return 0;
}
}
}
}
}
#endif
if (wa_oldWndProc)
return CallWindowProc(wa_oldWndProc, hwnd, uMsg, wParam, lParam);
else
return 0;
}
void BuildLoader(HWND winampWindow)
{
if (IsWindowUnicode(winampWindow))
wa_oldWndProc=(WNDPROC) SetWindowLongPtrW(winampWindow,GWLP_WNDPROC,(LONG_PTR)LoaderProc);
else
wa_oldWndProc=(WNDPROC) SetWindowLongPtrA(winampWindow,GWLP_WNDPROC,(LONG_PTR)LoaderProc);
}
void DestroyLoader(HWND winampWindow)
{
//if (wa_oldWndProc)
// SetWindowLong(winampWindow,GWL_WNDPROC,(LONG)wa_oldWndProc);
//wa_oldWndProc=0;
}

View File

@ -0,0 +1,6 @@
#ifndef NULLSOFT_LOADERH
#define NULLSOFT_LOADERH
void BuildLoader(HWND winampWindow);
#endif

View File

@ -0,0 +1,469 @@
#include "main.h"
#include "Cloud.h"
#include "DownloadThread.h"
#include "OPMLParse.h"
#include "FeedsDialog.h"
#include "DownloadsParse.h"
#include "XMLWriter.h"
#include "FeedParse.h"
#include "DownloadsDialog.h"
#include "Preferences.h"
#include "..\..\General\gen_ml/ml.h"
#include "Defaults.h"
#include "Wire.h"
#include "..\..\General\gen_ml/ml_ipc_0313.h"
#include "RSSCOM.h"
#include "api__ml_wire.h"
#include "Downloaded.h"
#include "DownloadStatus.h"
#include "Factory.h"
#include "JSAPI2_Creator.h"
#include "./navigation.h"
#include <strsafe.h>
#include "PCastFactory.h"
Cloud cloud;
WireManager channelMgr;
int treeId = 0, allId = 0
#if 0
, discoverId = 0
#endif
;
MLTREEITEMW downloadsTree;
wchar_t downloadsStr[64] = {0}, *ml_cfg = 0,
feedsXmlFileName[1024] = {0},
feedsXmlFileNameBackup[1024] = {0},
rssXmlFileName[1024] = {0},
rssXmlFileNameBackup[1024] = {0};
ATOM VIEWPROP = 0;
api_downloadManager *WAC_API_DOWNLOADMANAGER = 0;
static int Init();
static void Quit();
static INT_PTR MessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
DWORD threadStorage=TLS_OUT_OF_INDEXES;
extern "C" winampMediaLibraryPlugin plugin =
{
MLHDR_VER,
"nullsoft(ml_wire.dll)",
Init,
Quit,
MessageProc,
0,
0,
0,
};
static prefsDlgRecW preferences;
static wchar_t preferencesName[64] = {0};
void SaveChannels(ChannelList &channels)
{
// generate a backup of the feeds.xml to cope with it being wiped randomly for some
// people or it not being able to be saved in time e.g. during a forced OS shutdown
CopyFile(feedsXmlFileName, feedsXmlFileNameBackup, FALSE);
SaveChannels(feedsXmlFileName, channels);
}
void SaveAll(bool rss_only)
{
if (AGAVE_API_STATS)
AGAVE_API_STATS->SetStat(api_stats::PODCAST_COUNT, (int)channels.size());
if(!rss_only)
SaveChannels(channels);
// generate a backup of the rss.xml to cope with it being wiped randomly for some
// people or it not being able to be saved in time e.g. during a forced OS shutdown
CopyFile(rssXmlFileName, rssXmlFileNameBackup, FALSE);
SaveSettings(rssXmlFileName, downloadedFiles);
}
static PodcastsFactory podcastsFactory;
HANDLE hMainThread = NULL;
HCURSOR hDragNDropCursor = NULL;
int winampVersion = 0;
JSAPI2::api_security *AGAVE_API_JSAPI2_SECURITY = 0;
JSAPI2Factory jsapi2Creator;
obj_ombrowser *browserManager = NULL;
api_application *applicationApi = NULL;
api_stats *AGAVE_API_STATS = 0;
api_threadpool *WASABI_API_THREADPOOL = 0;
api_explorerfindfile *WASABI_API_EXPLORERFINDFILE = 0;
// wasabi based services for localisation support
api_language *WASABI_API_LNG = 0;
HINSTANCE WASABI_API_LNG_HINST = 0;
HINSTANCE WASABI_API_ORIG_HINST = 0;
static PCastFactory pcastFactory;
static void CALLBACK InitTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
{
KillTimer(hwnd, eventId);
{
Nullsoft::Utility::AutoLock lock (channels LOCKNAME("feeds.xml load!"));
FeedParse downloader(&channelMgr, true);
downloader.DownloadFile(feedsXmlFileName);
if (AGAVE_API_STATS)
AGAVE_API_STATS->SetStat(api_stats::PODCAST_COUNT, (int)channels.size());
}
if (updateOnLaunch)
{
cloud.RefreshAll();
}
cloud.Init();
cloud.Pulse();
}
int Init()
{
hMainThread = GetCurrentThread();
hDragNDropCursor = LoadCursor( GetModuleHandle( L"gen_ml.dll" ), MAKEINTRESOURCE( ML_IDC_DRAGDROP ) );
threadStorage = TlsAlloc();
if ( 0 == VIEWPROP )
{
VIEWPROP = GlobalAddAtom( L"Nullsoft_PodcastView" );
if ( VIEWPROP == 0 )
return 1;
}
winampVersion = SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETVERSION );
ml_cfg = (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW);
plugin.service->service_register( &podcastsFactory );
plugin.service->service_register( &jsapi2Creator );
plugin.service->service_register( &pcastFactory );
// loader so that we can get the localisation service api for use
waServiceFactory *sf = plugin.service->service_getServiceByGuid( languageApiGUID );
if ( sf )
WASABI_API_LNG = reinterpret_cast<api_language*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( JSAPI2::api_securityGUID );
if ( sf )
AGAVE_API_JSAPI2_SECURITY = reinterpret_cast<JSAPI2::api_security*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
if ( sf )
WASABI_API_APP = reinterpret_cast<api_application*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( OBJ_OmBrowser );
if ( sf )
OMBROWSERMNGR = reinterpret_cast<obj_ombrowser*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( AnonymousStatsGUID );
if ( sf )
AGAVE_API_STATS = reinterpret_cast<api_stats*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( ThreadPoolGUID );
if ( sf )
WASABI_API_THREADPOOL = reinterpret_cast<api_threadpool*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( DownloadManagerGUID );
if ( sf )
WAC_API_DOWNLOADMANAGER = reinterpret_cast<api_downloadManager*>( sf->getInterface() );
sf = plugin.service->service_getServiceByGuid( ExplorerFindFileApiGUID );
if ( sf )
WASABI_API_EXPLORERFINDFILE = reinterpret_cast<api_explorerfindfile*>( sf->getInterface() );
// need to have this initialised before we try to do anything with localisation features
WASABI_API_START_LANG( plugin.hDllInstance, MlWireLangGUID );
static wchar_t szDescription[ 256 ];
StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_PLUGIN_NAME ), PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR );
plugin.description = (char*)szDescription;
mediaLibrary.library = plugin.hwndLibraryParent;
mediaLibrary.winamp = plugin.hwndWinampParent;
mediaLibrary.instance = plugin.hDllInstance;
RssCOM *rss;
if (SUCCEEDED(RssCOM::CreateInstance(&rss)))
{
DispatchInfo dispatchInfo;
dispatchInfo.name = (LPWSTR)rss->GetName();
dispatchInfo.dispatch = rss;
SENDWAIPC(plugin.hwndWinampParent, IPC_ADD_DISPATCH_OBJECT, (WPARAM)&dispatchInfo);
rss->Release();
}
BuildDefaultDownloadPath( plugin.hwndWinampParent );
preferences.hInst = WASABI_API_LNG_HINST;
preferences.dlgID = IDD_PREFERENCES;
preferences.proc = (void *)PreferencesDialogProc;
preferences.name = WASABI_API_LNGSTRINGW_BUF( IDS_PODCAST_DIRECTORY, preferencesName, 64 );
preferences.where = 6;
mediaLibrary.AddPreferences( preferences );
wchar_t g_path[MAX_PATH] = {0};
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds", g_path, MAX_PATH );
CreateDirectoryW( g_path, NULL );
wchar_t oldxmlFileName[1024] = {0}, oldxmlFileNameBackup[ 1024 ] = { 0 };
mediaLibrary.BuildPath( L"Plugins\\ml\\rss.xml", oldxmlFileName, 1024 );
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\rss.xml", rssXmlFileName, 1024 );
mediaLibrary.BuildPath( L"Plugins\\ml\\rss.xml.backup", oldxmlFileNameBackup, 1024 );
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\rss.xml.backup", rssXmlFileNameBackup, 1024 );
if ( PathFileExists( oldxmlFileName ) && !PathFileExists(rssXmlFileName))
{
MoveFile( oldxmlFileName, rssXmlFileName );
MoveFile( oldxmlFileNameBackup, rssXmlFileNameBackup );
}
{
DownloadsParse downloader;
downloader.DownloadFile(rssXmlFileName);
}
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds.xml", oldxmlFileName, 1024 );
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\feeds.xml", feedsXmlFileName, 1024 );
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds.xml.backup", oldxmlFileNameBackup, 1024 );
mediaLibrary.BuildPath( L"Plugins\\ml\\feeds\\feeds.xml.backup", feedsXmlFileNameBackup, 1024 );
if ( PathFileExists( oldxmlFileName ) && !PathFileExists( feedsXmlFileName ) )
{
MoveFile( oldxmlFileName, feedsXmlFileName );
MoveFile( oldxmlFileNameBackup, feedsXmlFileNameBackup );
}
Navigation_Initialize();
SetTimer( plugin.hwndLibraryParent, 0x498, 10, InitTimer );
return 0;
}
void Quit()
{
// If there are still files downloading, cancel download to remove incomplete downloaded files
while ( downloadStatus.CurrentlyDownloading() )
{
Nullsoft::Utility::AutoLock lock( downloadStatus.statusLock );
DownloadToken dltoken = downloadStatus.downloads.begin()->first;
WAC_API_DOWNLOADMANAGER->CancelDownload( dltoken );
}
cloud.Quit();
CloseDatabase();
plugin.service->service_deregister( &podcastsFactory );
plugin.service->service_deregister( &jsapi2Creator );
waServiceFactory *sf = plugin.service->service_getServiceByGuid( OBJ_OmBrowser );
if ( sf != NULL )
sf->releaseInterface( OMBROWSERMNGR );
sf = plugin.service->service_getServiceByGuid( applicationApiServiceGuid );
if ( sf != NULL )
sf->releaseInterface(WASABI_API_APP);
sf = plugin.service->service_getServiceByGuid(AnonymousStatsGUID);
if ( sf != NULL )
sf->releaseInterface(AGAVE_API_STATS);
sf = plugin.service->service_getServiceByGuid(ThreadPoolGUID);
if ( sf != NULL )
sf->releaseInterface(WASABI_API_THREADPOOL);
sf = plugin.service->service_getServiceByGuid(ExplorerFindFileApiGUID);
if ( sf != NULL )
sf->releaseInterface(WASABI_API_EXPLORERFINDFILE);
sf = plugin.service->service_getServiceByGuid(DownloadManagerGUID);
if ( sf != NULL )
sf->releaseInterface(WAC_API_DOWNLOADMANAGER);
if ( VIEWPROP != 0 )
{
GlobalDeleteAtom(VIEWPROP);
VIEWPROP = 0;
}
}
static INT_PTR Podcast_OnContextMenu( INT_PTR param1, HWND hHost, POINTS pts)
{
HNAVITEM hItem = (HNAVITEM)param1;
HNAVITEM myItem = Navigation_FindService( SERVICE_PODCAST, NULL, NULL);
HNAVITEM podcastItem = MLNavItem_GetChild( plugin.hwndLibraryParent, myItem);
HNAVITEM subscriptionItem = Navigation_FindService( SERVICE_SUBSCRIPTION, podcastItem, NULL);
if ( hItem != myItem && hItem != subscriptionItem )
return FALSE;
POINT pt;
POINTSTOPOINT( pt, pts );
if ( pt.x == -1 || pt.y == -1 )
{
NAVITEMGETRECT itemRect;
itemRect.fItem = FALSE;
itemRect.hItem = hItem;
if ( MLNavItem_GetRect( plugin.hwndLibraryParent, &itemRect ) )
{
MapWindowPoints( hHost, HWND_DESKTOP, (POINT*)&itemRect.rc, 2 );
pt.x = itemRect.rc.left + 2;
pt.y = itemRect.rc.top + 2;
}
}
HMENU hMenu = WASABI_API_LOADMENU( IDR_MENU1 );
int subMenuId = ( hItem == subscriptionItem )? 4 : 3;
HMENU subMenu = ( NULL != hMenu ) ? GetSubMenu( hMenu, subMenuId ) : NULL;
if ( subMenu != NULL )
{
INT r = Menu_TrackPopup( plugin.hwndLibraryParent, subMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTBUTTON, pt.x, pt.y, hHost, NULL );
switch(r)
{
case ID_NAVIGATION_DIRECTORY:
MLNavItem_Select( plugin.hwndLibraryParent, myItem );
break;
case ID_NAVIGATION_PREFERENCES:
SENDWAIPC( plugin.hwndWinampParent, IPC_OPENPREFSTOPAGE, &preferences );
break;
case ID_NAVIGATION_HELP:
SENDWAIPC( plugin.hwndWinampParent, IPC_OPEN_URL, L"https://help.winamp.com/hc/articles/8112346487060-Podcast-Directory" );
break;
case ID_NAVIGATION_REFRESHALL:
cloud.RefreshAll();
cloud.Pulse();
break;
}
}
if ( hMenu != NULL )
DestroyMenu( hMenu );
return TRUE;
}
INT_PTR MessageProc( int msg, INT_PTR param1, INT_PTR param2, INT_PTR param3 )
{
INT_PTR result = 0;
if ( Navigation_ProcessMessage( msg, param1, param2, param3, &result ) != FALSE )
return result;
switch ( msg )
{
case ML_MSG_NOTOKTOQUIT:
{
if (downloadStatus.CurrentlyDownloading())
{
wchar_t titleStr[32] = {0};
if ( MessageBox( plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW( IDS_CANCEL_DOWNLOADS_AND_QUIT ), WASABI_API_LNGSTRINGW_BUF( IDS_CONFIRM_QUIT, titleStr, 32 ), MB_YESNO | MB_ICONQUESTION ) == IDNO )
return TRUE;
}
return 0;
}
case ML_MSG_CONFIG:
mediaLibrary.GoToPreferences(preferences._id);
return TRUE;
case ML_MSG_NAVIGATION_CONTEXTMENU:
return Podcast_OnContextMenu( param1, (HWND)param2, MAKEPOINTS( param3 ) );
case ML_MSG_VIEW_PLAY_ENQUEUE_CHANGE:
enqueuedef = param1;
groupBtn = param2;
PostMessage( current_window, WM_APP + 104, param1, param2 );
return 0;
}
return FALSE;
}
#define TREE_IMAGE_LOCAL_PODCASTS 108
void addToLibrary(const DownloadedFile& d)
{
itemRecordW item = { 0 };
item.year = -1;
item.track = -1;
item.tracks = -1;
item.length = -1;
item.rating = -1;
item.lastplay = -1;
item.lastupd = -1;
item.filetime = -1;
item.filesize = -1;
item.bitrate = -1;
item.type = -1;
item.disc = -1;
item.discs = -1;
item.bpm = -1;
item.playcount = -1;
item.filename = _wcsdup( d.path );
setRecordExtendedItem(&item,L"ispodcast",L"1");
setRecordExtendedItem(&item,L"podcastchannel",d.channel);
wchar_t buf[40] = {0};
_i64tow(d.publishDate,buf,10);
if(d.publishDate)
setRecordExtendedItem(&item,L"podcastpubdate",buf);
LMDB_FILE_ADD_INFOW fai = {item.filename,-1,-1};
SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&fai, ML_IPC_DB_ADDORUPDATEFILEW);
SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&item, ML_IPC_DB_UPDATEITEMW);
PostMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_DB_SYNCDB);
freeRecord(&item);
if(needToMakePodcastsView) {
mlSmartViewInfo m = { sizeof( mlSmartViewInfo ),2,L"Podcasts", L"ispodcast = 1",461315,TREE_IMAGE_LOCAL_PODCASTS,0 };
WASABI_API_LNGSTRINGW_BUF( IDS_PODCASTS, m.smartViewName, 128 );
SendMessage( plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&m, ML_IPC_SMARTVIEW_ADD );
needToMakePodcastsView = false;
}
}
typedef struct
{
const DownloadedFile *d;
volatile UINT done;
} apc_addtolib_waiter;
static VOID CALLBACK apc_addtolib(ULONG_PTR dwParam)
{
apc_addtolib_waiter *w = (apc_addtolib_waiter *)dwParam;
addToLibrary(*w->d);
w->done=1;
}
void addToLibrary_thread(const DownloadedFile& d)
{
apc_addtolib_waiter w = { &d, 0 };
if ( !QueueUserAPC( apc_addtolib, hMainThread, (ULONG_PTR)&w ) )
return;
while ( !w.done )
SleepEx( 5, true );
}
extern "C" __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
{
return &plugin;
}

View File

@ -0,0 +1,69 @@
#ifndef NULLSOFT_MAINH
#define NULLSOFT_MAINH
#include "Wire.h"
#include "Downloaded.h"
#define PLUGIN_VERSION_MAJOR 1
#define PLUGIN_VERSION_MINOR 80
#define SERVICE_PODCAST 720
#define SERVICE_SUBSCRIPTION 721
#define SERVICE_DOWNLOADS 722
#define BAD_CHANNEL ((size_t)-1)
#define BAD_ITEM ((size_t)-1)
void SaveChannels(ChannelList &channels);
void SaveAll(bool rss_only=false);
void HookTerminate();
void DestroyLoader(HWND);
void BuildLoader(HWND);
extern int winampVersion;
void addToLibrary(const DownloadedFile& d); // call in winamp main thread only
void addToLibrary_thread(const DownloadedFile& d); // call from any thread
bool AddPodcastData(const DownloadedFile &data);
bool IsPodcastDownloaded(const wchar_t *url);
void CloseDatabase();
#include "resource.h"
#include "../nu/DialogSkinner.h"
#include "../nu/MediaLibraryInterface.h"
#include "../nu/AutoChar.h"
#include "../nu/AutoWide.h"
#include "../nu/AutoLock.h"
#include "..\..\General\gen_ml/menu.h"
#include <windows.h>
#include <shlwapi.h>
extern ATOM VIEWPROP;
extern winampMediaLibraryPlugin plugin;
#include "../Components/wac_downloadManager/wac_downloadManager_api.h"
#define ML_ENQDEF_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"enqueuedef", 0, ml_cfg))
#define ML_GROUPBTN_VAL() (!!GetPrivateProfileInt(L"gen_ml_config", L"groupbtn", 1, ml_cfg))
extern wchar_t* ml_cfg;
wchar_t *urlencode(wchar_t *p);
extern HWND current_window;
extern int groupBtn, enqueuedef, customAllowed;
extern viewButtons view;
void SwapPlayEnqueueInMenu(HMENU listMenu);
void SyncMenuWithAccelerators(HWND hwndDlg, HMENU menu);
void Downloads_UpdateButtonText(HWND hwndDlg, int _enqueuedef);
void listbuild(wchar_t **buf, int &buf_size, int &buf_pos, const wchar_t *tbuf);
enum
{
BPM_ECHO_WM_COMMAND=0x1, // send WM_COMMAND and return value
BPM_WM_COMMAND = 0x2, // just send WM_COMMAND
};
BOOL Downloads_ButtonPopupMenu(HWND hwndDlg, int buttonId, HMENU menu, int flags=0);
void UpdateMenuItems(HWND hwndDlg, HMENU menu);
#endif

View File

@ -0,0 +1,7 @@
#include "main.h"
#include "MessageProcessor.h"
#define CBCLASS MessageProcessor
START_DISPATCH;
CB(API_MESSAGEPROCESSOR_PROCESS_MESSAGE, ProcessMessage)
END_DISPATCH;

View File

@ -0,0 +1,35 @@
#ifndef NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
#define NULLSOFT_ML_WIRE_MESSAGEPROCESSOR_H
#include <api/application/api_messageprocessor.h>
#include "main.h"
#ifndef WM_FORWARDMSG
#define WM_FORWARDMSG 0x037F
#endif
class MessageProcessor : public api_messageprocessor
{
public:
bool ProcessMessage(MSG *msg)
{
if (msg->message < WM_KEYFIRST || msg->message > WM_KEYLAST)
return false;
HWND hWndCtl = ::GetFocus();
if (IsChild(browserHWND, hWndCtl))
{
// find a direct child of the dialog from the window that has focus
while(::GetParent(hWndCtl) != browserHWND)
hWndCtl = ::GetParent(hWndCtl);
if (activeBrowser->translateKey(*msg))
return true;
}
return false;
}
protected:
RECVS_DISPATCH;
};
#endif

View File

@ -0,0 +1,33 @@
#if 0
#include "main.h"
#include "OPMLParse.h"
#ifdef DEBUG
#include <iostream>
static void DisplayNodes(XMLNode &node)
{
XMLNode::NodeMap::iterator nodeItr;
for (nodeItr = node.nodes.begin();nodeItr != node.nodes.end(); nodeItr++)
{
for (XMLNode::NodeList::iterator itr = nodeItr->second.begin(); itr != nodeItr->second.end(); itr++)
{
std::wcerr << L"<" << nodeItr->first << L">" << std::endl;
DisplayNodes(**itr);
std::wcerr << L"</" << nodeItr->first << L">" << std::endl;
}
}
}
#endif
void OPMLParse::ReadNodes(const wchar_t *url)
{
// DisplayNodes(xmlNode);
Alias<XMLNode> curNode;
curNode = xmlNode.Get(L"opml");
if (!curNode)
return;
}
#endif

View File

@ -0,0 +1,28 @@
#ifndef NULLSOFT_OPMLPARSEH
#define NULLSOFT_OPMLPARSEH
#include "DownloadThread.h"
#include "ChannelSync.h"
class OPMLParse : public DownloadThread
{
public:
OPMLParse(ChannelSync *_sync)
: sync(_sync)
{
}
~OPMLParse()
{
sync = 0;
}
virtual void ReadNodes(const wchar_t *url);
private:
ChannelSync *sync;
};
#endif

View File

@ -0,0 +1,60 @@
#include "api__ml_wire.h"
#include ".\pcastfactory.h"
#include "Wire.h"
static PCastURIHandler PCastUH;
static const char serviceName[] = "PCast";
FOURCC PCastFactory::GetServiceType()
{
return svc_urihandler::getServiceType();
}
const char *PCastFactory::GetServiceName()
{
return serviceName;
}
void *PCastFactory::GetInterface(int global_lock)
{
// if (global_lock)
// plugin.service->service_lock(this, (void *)ifc);
return &PCastUH;
}
int PCastFactory::SupportNonLockingInterface()
{
return 1;
}
int PCastFactory::ReleaseInterface(void *ifc)
{
//plugin.service->service_unlock(ifc);
return 1;
}
const char *PCastFactory::GetTestString()
{
return NULL;
}
int PCastFactory::ServiceNotify(int msg, int param1, int param2)
{
return 0;
}
#ifdef CBCLASS
#undef CBCLASS
#endif
#define CBCLASS PCastFactory
START_DISPATCH;
CB( WASERVICEFACTORY_GETSERVICETYPE, GetServiceType )
CB( WASERVICEFACTORY_GETSERVICENAME, GetServiceName )
CB( WASERVICEFACTORY_GETINTERFACE, GetInterface )
CB( WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface )
CB( WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface )
CB( WASERVICEFACTORY_GETTESTSTRING, GetTestString )
CB( WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify )
END_DISPATCH;

View File

@ -0,0 +1,24 @@
#pragma once
#include "api__ml_wire.h"
#include "api/service/waservicefactory.h"
#include "api/service/services.h"
#include "PCastURIHandler.h"
class PCastFactory : public waServiceFactory
{
public:
FOURCC GetServiceType();
const char *GetServiceName();
GUID GetGUID();
void *GetInterface(int global_lock);
int SupportNonLockingInterface();
int ReleaseInterface(void *ifc);
const char *GetTestString();
int ServiceNotify(int msg, int param1, int param2);
protected:
RECVS_DISPATCH;
};

View File

@ -0,0 +1,258 @@
#include "main.h"
#include "./pcasturihandler.h"
#include "./Feeds.h"
#include "./FeedUtil.h"
#include "../nu/AutoLock.h"
#include "./wire.h"
#include "./errors.h"
//#include "../Agave/URIHandler/svc_urihandler.h"
//#include <api/service/waservicefactory.h>
#include "api__ml_wire.h"
#include "./cloud.h"
#include "./SubscriptionView.h"
#include "./resource.h"
#include "navigation.h"
#include "..\..\General\gen_ml/ml_ipc_0313.h"
#include <strsafe.h>
using namespace Nullsoft::Utility;
extern ChannelList channels;
extern Cloud cloud;
static uint8_t quickhex(wchar_t c)
{
int hexvalue = c;
if (hexvalue & 0x10)
hexvalue &= ~0x30;
else
{
hexvalue &= 0xF;
hexvalue += 9;
}
return hexvalue;
}
static uint8_t DecodeEscape(const wchar_t *&str)
{
uint8_t a = quickhex(*++str);
uint8_t b = quickhex(*++str);
str++;
return a * 16 + b;
}
static void DecodeEscapedUTF8(wchar_t *&output, const wchar_t *&input)
{
uint8_t utf8_data[1024] = {0}; // hopefully big enough!!
int num_utf8_words=0;
bool error=false;
while (input && *input == '%' && num_utf8_words < sizeof(utf8_data))
{
if (iswxdigit(input[1]) && iswxdigit(input[2]))
{
utf8_data[num_utf8_words++]=DecodeEscape(input);
}
else if (input[1] == '%')
{
input+=2;
utf8_data[num_utf8_words++]='%';
}
else
{
error = true;
break;
}
}
int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, 0, 0);
MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)utf8_data, num_utf8_words, output, len);
output += len;
if (error)
{
*output++ = *input++;
}
}
static void UrlDecode(const wchar_t *input, wchar_t *output, size_t len)
{
const wchar_t *stop = output+len-4; // give ourself a cushion large enough to hold a full UTF-16 sequence
const wchar_t *itr = input;
while (itr && *itr)
{
if (output >= stop)
{
*output=0;
return;
}
switch (*itr)
{
case '%':
DecodeEscapedUTF8(output, itr);
break;
case '&':
*output = 0;
return;
default:
*output++ = *itr++;
break;
}
}
*output = 0;
}
// first parameter has param name either null or = terminated, second is null terminated
static bool ParamCompare(const wchar_t *url_param, const wchar_t *param_name)
{
while (url_param && *url_param && *param_name && *url_param!=L'=')
{
if (*url_param++ != *param_name++)
return false;
}
return true;
}
static bool get_request_parm(const wchar_t *params, const wchar_t *param_name, wchar_t *value, size_t value_len)
{
size_t param_name_len = wcslen(param_name);
const wchar_t *t=params;
while (t && *t && *t != L'?') // find start of parameters
t++;
while (t && *t)
{
t++; // skip ? or &
if (ParamCompare(t, param_name))
{
while (t && *t && *t != L'=' && *t != '&') // find start of value
t++;
switch(*t)
{
case L'=':
UrlDecode(++t, value, value_len);
return true;
case 0:
case L'&': // no value
*value=0;
return true;
default: // shouldn't get here
return false;
}
}
while (t && *t && *t != L'&') // find next parameter
t++;
}
return false;
}
int PCastURIHandler::ProcessFilename(const wchar_t *filename)
{
if (
(wcsnicmp(filename, L"pcast://", 8)) == 0 ||
(wcsnicmp(filename, L"feed://", 7) == 0) ||
(wcsnicmp(filename, L"winamp://Podcast/Subscribe", 26) == 0) ||
(wcsnicmp(filename, L"winamp://Podcast/Search", 23) == 0)
)
{
wchar_t *tempFilename = NULL;
wchar_t url[1024] = {0};
if (wcsnicmp(filename, L"winamp://Podcast/Subscribe", 26) == 0)
{
// extract/decode and use the url= parameter
if (get_request_parm(filename, L"url", url, 1024) && url[0])
{
tempFilename = wcsdup(url);
}
else
{
// did not find a url parameter
return NOT_HANDLED;
}
}
else if (wcsnicmp(filename, L"winamp://Podcast/Search", 23) == 0)
{
// TODO: maybe: if (get_request_parm(filename, L"url", url, 1024) && url[0])
{
HNAVITEM hItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL);
MLNavItem_Select(plugin.hwndLibraryParent, hItem);
return HANDLED;
}
/*
else
{
// did not find a url parameter
return NOT_HANDLED;
}
*/
}
else
{
// Use the full filename
tempFilename = wcsdup(filename);
}
// subscription confirmation
WCHAR szText[1024] = {0}, szBuffer[1024] = {0};
WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_SUBSCRIPTION_PROMP, szBuffer, ARRAYSIZE(szBuffer));
StringCchPrintf(szText, ARRAYSIZE(szText), szBuffer, tempFilename);
WASABI_API_LNGSTRINGW_BUF(IDS_PODCAST_SUBSCRIPTION_HEADER, szBuffer, ARRAYSIZE(szBuffer));
if (IDYES == MessageBox(plugin.hwndLibraryParent, szText, szBuffer, MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_SETFOREGROUND) )
{
// add feed to channels, pulse the cloud, refresh the UI pane.
Channel newFeed;
newFeed.SetURL(tempFilename);
if (DownloadFeedInformation(newFeed)==DOWNLOAD_SUCCESS)
{
channels.channelGuard.Lock();
channels.AddChannel(newFeed);
channels.channelGuard.Unlock();
cloud.Pulse();
HWND hView = SubscriptionView_FindWindow();
if (NULL != hView)
{
SubscriptionView_RefreshChannels(hView, TRUE);
}
else
{
HNAVITEM myItem = Navigation_FindService(SERVICE_PODCAST, NULL, NULL);
HNAVITEM podcastItem = MLNavItem_GetChild(plugin.hwndLibraryParent, myItem);
HNAVITEM subscriptionItem = Navigation_FindService(SERVICE_SUBSCRIPTION, podcastItem, NULL);
MLNavItem_Select(plugin.hwndLibraryParent, subscriptionItem);
}
}
free(tempFilename);
return HANDLED;
}
else
free(tempFilename);
}
return NOT_HANDLED;
}
int PCastURIHandler::IsMine(const wchar_t *filename)
{
int i = 0;
if (
(wcsnicmp(filename, L"pcast://", 8)) == 0 ||
(wcsnicmp(filename, L"feed://", 7) == 0) ||
(wcsnicmp(filename, L"winamp://Podcast/Subscribe", 26) == 0) ||
(wcsnicmp(filename, L"winamp://Podcast/Search", 23) == 0)
)
return HANDLED;
else
return NOT_HANDLED;
}
#define CBCLASS PCastURIHandler
START_DISPATCH;
CB(PROCESSFILENAME, ProcessFilename);
CB(ISMINE, IsMine);
END_DISPATCH;
#undef CBCLASS

View File

@ -0,0 +1,21 @@
#pragma once
#include "../Agave/URIHandler/svc_urihandler.h"
// {DA5F8547-5D53-49d9-B9EC-C59318D11798}
static const GUID pcast_uri_handler_guid =
{ 0xda5f8547, 0x5d53, 0x49d9, { 0xb9, 0xec, 0xc5, 0x93, 0x18, 0xd1, 0x17, 0x98 } };
class PCastURIHandler :
public svc_urihandler
{
public:
static const char *getServiceName() { return "PCast URI Handler"; }
static GUID getServiceGuid() { return pcast_uri_handler_guid; }
int ProcessFilename(const wchar_t *filename);
int IsMine(const wchar_t *filename); // just like ProcessFilename but don't actually process
protected:
RECVS_DISPATCH;
};

View File

@ -0,0 +1,43 @@
#include "ParseUtil.h"
bool PropertyIsTrue( const XMLNode *item, const wchar_t *property )
{
if ( !item )
return false;
const wchar_t *value = item->GetProperty( property );
if ( !value )
return false;
return !_wcsicmp( value, L"true" );
}
bool PropertyIsFalse( const XMLNode *item, const wchar_t *property )
{
if ( !item )
return false;
const wchar_t *value = item->GetProperty( property );
if ( !value )
return false;
return !_wcsicmp( value, L"false" );
}
const wchar_t *GetContent( const XMLNode *item, const wchar_t *tag )
{
const XMLNode *curNode = item->Get( tag );
if ( curNode )
return curNode->GetContent();
else
return 0;
}
const wchar_t *GetProperty( const XMLNode *item, const wchar_t *tag, const wchar_t *property )
{
const XMLNode *curNode = item->Get( tag );
if ( curNode )
return curNode->GetProperty( property );
else
return 0;
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "../xml/XMLNode.h"
#include <bfc/platform/types.h>
bool PropertyIsTrue(const XMLNode *item, const wchar_t *property);
// not necessarily the opposite of PropertyIsTrue (returns false when field is empty
bool PropertyIsFalse(const XMLNode *item, const wchar_t *property);
const wchar_t *GetContent(const XMLNode *item, const wchar_t *tag);
const wchar_t *GetProperty(const XMLNode *item, const wchar_t *tag, const wchar_t *property);

View File

@ -0,0 +1,160 @@
#include "main.h"
#include "api__ml_wire.h"
#include "../winamp/wa_ipc.h"
#include "Defaults.h"
#include "UpdateTime.h"
#include "UpdateAutoDownload.h"
#include "./cloud.h"
#include <shlobj.h>
extern Cloud cloud;
void Preferences_Init(HWND hwndDlg)
{
WCHAR szBuffer[256] = {0};
for (int i = 0;i < Update::TIME_NUMENTRIES;i++)
{
const wchar_t *str = Update::GetTitle(i, szBuffer, ARRAYSIZE(szBuffer));
SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_ADDSTRING, 0, (LPARAM) str);
}
int selection = Update::GetSelection(updateTime, autoUpdate);
SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_SETCURSEL, selection, 0);
WCHAR adBuffer[256] = {0};
for (int i = 0;i < UpdateAutoDownload::AUTODOWNLOAD_NUMENTRIES;i++)
{
const wchar_t *str = UpdateAutoDownload::GetTitle(i, adBuffer, ARRAYSIZE(adBuffer));
SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_ADDSTRING, 0, (LPARAM) str);
}
selection = UpdateAutoDownload::GetSelection(autoDownloadEpisodes, autoDownload);
SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_SETCURSEL, selection, 0);
SetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath);
SetDlgItemText(hwndDlg, IDC_DIRECTORYURL, serviceUrl);
CheckDlgButton(hwndDlg, IDC_UPDATEONLAUNCH, updateOnLaunch?BST_CHECKED:BST_UNCHECKED);
}
BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
{
wchar_t cl[32] = {0};
GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
if (!lstrcmpiW(cl, WC_TREEVIEW))
{
PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
return FALSE;
}
return TRUE;
}
int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED)
{
SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)defaultDownloadPath);
// this is not nice but it fixes the selection not working correctly on all OSes
EnumChildWindows(hwnd, browseEnumProc, 0);
}
return 0;
}
void Preferences_Browse(HWND hwndDlg)
{
wchar_t folder[MAX_PATH] = {0};
BROWSEINFO browse = {0};
lstrcpyn(folder, defaultDownloadPath, MAX_PATH);
browse.hwndOwner = hwndDlg;
browse.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_FOLDER);
browse.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
browse.lpfn = BrowseCallbackProc;
LPITEMIDLIST itemList = SHBrowseForFolder(&browse);
if (itemList)
{
SHGetPathFromIDList(itemList, folder);
lstrcpyn(defaultDownloadPath, folder, MAX_PATH);
SetWindowText(GetDlgItem(hwndDlg, IDC_DOWNLOADLOCATION), folder);
LPMALLOC malloc;
SHGetMalloc(&malloc);
malloc->Free(itemList);
}
}
void Preferences_UpdateOnLaunch( HWND hwndDlg )
{
updateOnLaunch = ( IsDlgButtonChecked( hwndDlg, IDC_UPDATEONLAUNCH ) == BST_CHECKED );
}
void Preferences_UpdateList(HWND hwndDlg)
{
LRESULT timeSelection;
timeSelection = SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_GETCURSEL, 0, 0);
if (timeSelection != CB_ERR)
{
autoUpdate = Update::GetAutoUpdate(timeSelection);
updateTime = Update::GetTime(timeSelection);
if ( autoUpdate )
cloud.Pulse(); // update the waitable timer
}
}
void Preferences_AutoDownloadList(HWND hwndDlg)
{
LRESULT episodeSelection;
episodeSelection = SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_GETCURSEL, 0, 0);
if (episodeSelection != CB_ERR)
{
autoDownload = UpdateAutoDownload::GetAutoDownload(episodeSelection);
autoDownloadEpisodes = UpdateAutoDownload::GetAutoDownloadEpisodes(episodeSelection);
}
}
BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
Preferences_Init(hwndDlg);
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_BROWSE:
Preferences_Browse(hwndDlg);
break;
case IDC_UPDATELIST:
Preferences_UpdateList(hwndDlg);
break;
case IDC_AUTODOWNLOADLIST:
Preferences_AutoDownloadList(hwndDlg);
break;
case IDC_UPDATEONLAUNCH:
Preferences_UpdateOnLaunch(hwndDlg);
break;
case IDC_DOWNLOADLOCATION:
if (HIWORD(wParam) == EN_CHANGE)
{
GetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath, MAX_PATH);
if (!PathFileExists(defaultDownloadPath))
{
BuildDefaultDownloadPath(plugin.hwndWinampParent);
SetDlgItemText(hwndDlg, IDC_DOWNLOADLOCATION, defaultDownloadPath);
}
}
break;
case IDC_DIRECTORYURL:
if (HIWORD(wParam) == EN_CHANGE)
{
GetDlgItemText(hwndDlg, IDC_DIRECTORYURL, serviceUrl, ARRAYSIZE(serviceUrl));
}
break;
}
break;
case WM_DESTROY:
// ensure we save the changed settings
SaveAll(true);
break;
}
return 0;
}

View File

@ -0,0 +1,6 @@
#ifndef NULLSOFT_PREFERENCESH
#define NULLSOFT_PREFERENCESH
BOOL CALLBACK PreferencesDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
#endif

View File

@ -0,0 +1,216 @@
#include "main.h"
#include "RFCDate.h"
#include <strsafe.h>
void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len)
{
SYSTEMTIME sysTime = {0};
tm *newtime = _localtime64(&convertTime);
sysTime.wYear = newtime->tm_year + 1900;
sysTime.wMonth = newtime->tm_mon + 1;
sysTime.wDayOfWeek = newtime->tm_wday;
sysTime.wDay = newtime->tm_mday;
sysTime.wHour = newtime->tm_hour;
sysTime.wMinute = newtime->tm_min;
sysTime.wSecond = newtime->tm_sec;
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &sysTime, NULL, date_str, (int)len);
}
void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len)
{
SYSTEMTIME sysTime = {0};
tm *newtime = _gmtime64(&convertTime);
sysTime.wYear = newtime->tm_year + 1900;
sysTime.wMonth = newtime->tm_mon + 1;
sysTime.wDayOfWeek = newtime->tm_wday;
sysTime.wDay = newtime->tm_mday;
sysTime.wHour = newtime->tm_hour;
sysTime.wMinute = newtime->tm_min;
sysTime.wSecond = newtime->tm_sec;
wchar_t rfcDate[64] = {0}, rfcTime[64] = {0};
GetDateFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"ddd',' d MMM yyyy ", rfcDate, 64);
GetTimeFormat(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), 0, &sysTime, L"HH':'mm':'ss 'GMT'", rfcTime, 64);
StringCchPrintf(data_str,len,L"%s%s",rfcDate,rfcTime);
}
static int getMonthOfYear(const wchar_t *str);
static int validateTime(struct tm *tmloc, int gmoffset);
static const wchar_t *getNextField(const wchar_t *pos);
static int getGMOffset(const wchar_t *str);
enum
{
DAY_OF_MONTH = 0,
MONTH_OF_YEAR,
YEAR,
TIME,
TIMEZONE,
DATE_END
};
__time64_t MakeRFCDate(const wchar_t *date)
{
__time64_t result = 0;
const wchar_t *pos = date;
tm tmloc = {0};
const wchar_t *strmin;
const wchar_t *strsec;
int gmoffset = 1;
int state = DAY_OF_MONTH;
tzset();
/* skip weekday if present */
while (pos && *pos && !iswdigit(*pos)) pos++;
do
{
switch (state)
{
case DAY_OF_MONTH:
tmloc.tm_mday = _wtoi(pos);
break;
case MONTH_OF_YEAR:
tmloc.tm_mon = getMonthOfYear(pos);
break;
case YEAR:
{
/* TODO: we're only accepting 4-digit dates...*/
const wchar_t *test = pos; int numDigits = 0;
while (iswdigit(*test) && *test) { test++; numDigits++; }
if (numDigits == 2) // let's hope we never have 2 digit years!
tmloc.tm_year = _wtoi(pos) + 100; // assume 2 digit years are 20xx
else
tmloc.tm_year = _wtoi(pos) - 1900;
}
break;
case TIME:
strmin = wcschr(pos, L':');
strsec = strmin ? wcschr(strmin + 1, L':') : 0;
tmloc.tm_hour = _wtoi(pos);
if (!strmin) return _time64(0);
tmloc.tm_min = _wtoi(strmin + 1);
if (!strsec) return _time64(0);
tmloc.tm_sec = _wtoi(strsec + 1);
break;
case TIMEZONE:
gmoffset = getGMOffset(pos);
break;
case DATE_END:
pos = 0;
break;
}
state++;
}
while ((pos = getNextField(pos)));
tmloc.tm_isdst = 0; //_daylight;
if (validateTime(&tmloc, gmoffset))
{
result = _mktime64(&tmloc) - _timezone;
//if (_daylight)
}
return result;
}
const wchar_t *getNextField(const wchar_t *pos)
{
if (!pos)
return 0;
while (pos && *pos && !iswspace(*pos)) pos++;
while (pos && *pos && iswspace(*pos)) pos++;
return ((pos && *pos) ? pos : 0);
}
int validateTime(struct tm *tmloc, int gmoffset)
{
int result = 1;
if (tmloc->tm_mday < 1 || tmloc->tm_mday > 31 ||
tmloc->tm_mon < 0 || tmloc->tm_mon > 11 ||
tmloc->tm_year < 0 || tmloc->tm_year > 2000 ||
tmloc->tm_hour < 0 || tmloc->tm_hour > 23 ||
tmloc->tm_min < 0 || tmloc->tm_min > 59 ||
tmloc->tm_sec < 0 || tmloc->tm_sec > 59 ||
gmoffset == 1)
result = 0;
return result;
}
int getMonthOfYear(const wchar_t *str)
{
int mon = -1;
/* This is not the most efficient way to determine
the month (we could use integer comparisons, for instance)
but I don't think this will be a performance bottleneck :)
*/
if (!wcsnicmp(str, L"Jan", 3))
mon = 0;
else if (!wcsnicmp(str, L"Feb", 3))
mon = 1;
else if (!wcsnicmp(str, L"Mar", 3))
mon = 2;
else if (!wcsnicmp(str, L"Apr", 3))
mon = 3;
else if (!wcsnicmp(str, L"May", 3))
mon = 4;
else if (!wcsnicmp(str, L"Jun", 3))
mon = 5;
else if (!wcsnicmp(str, L"Jul", 3))
mon = 6;
else if (!wcsnicmp(str, L"Aug", 3))
mon = 7;
else if (!wcsnicmp(str, L"Sep", 3))
mon = 8;
else if (!wcsnicmp(str, L"Oct", 3))
mon = 9;
else if (!wcsnicmp(str, L"Nov", 3))
mon = 10;
else if (!wcsnicmp(str, L"Dec", 3))
mon = 11;
return mon;
}
int getGMOffset(const wchar_t *str)
{
int secs = 0;
/* See note in getMonthOfYear() */
if (!wcsnicmp(str, L"UT", 2) || !wcsnicmp(str, L"GMT", 3))
secs = 0;
else if (!wcsnicmp(str, L"EDT", 3))
secs = -4 * 3600;
else if (!wcsnicmp(str, L"PST", 3))
secs = -8 * 3600;
else if (!wcsnicmp(str, L"EST", 3) || !wcsnicmp(str, L"CDT", 3))
secs = -5 * 3600;
else if (!wcsnicmp(str, L"MST", 3) || !wcsnicmp(str, L"PDT", 3))
secs = -7 * 3600;
else if (!wcsnicmp(str, L"CST", 3) || !wcsnicmp(str, L"MDT", 3))
secs = -6 * 3600;
else if ( (str[0] == L'+' || str[0] == L'-') &&
iswdigit(str[1]) && iswdigit(str[2]) &&
iswdigit(str[3]) && iswdigit(str[4]))
{
secs = 3600 * (10 * (str[1] - 48) + str[2] - 48);
secs += 60 * (10 * (str[3] - 48) + str[4] - 48);
if (str[0] == L'-')
secs = -secs;
}
else
secs = 1;
return secs;
}

View File

@ -0,0 +1,7 @@
#ifndef NULLSOFT_RFCDATEH
#define NULLSOFT_RFCDATEH
void MakeRFCDateString(__time64_t convertTime, wchar_t *data_str, size_t len);
__time64_t MakeRFCDate(const wchar_t *date);
void MakeDateString(__time64_t convertTime, wchar_t *date_str, size_t len);
#endif

View File

@ -0,0 +1,232 @@
#include "main.h"
#include "./rssCOM.h"
#include "./util.h"
#include "api__ml_wire.h"
#include "./cloud.h"
#include "./feedUtil.h"
#include "./defaults.h"
#include "./errors.h"
#include "../winamp/jsapi.h"
#include "./rssCOM.h"
#include <strsafe.h>
extern Cloud cloud;
#define DISPTABLE_CLASS RssCOM
DISPTABLE_BEGIN()
DISPENTRY_ADD(DISPATCH_SUBSCRIBE, L"subscribe", OnSubscribe)
DISPTABLE_END
#undef DISPTABLE_CLASS
RssCOM::RssCOM()
{}
RssCOM::~RssCOM()
{}
HRESULT RssCOM::CreateInstance(RssCOM **instance)
{
if (NULL == instance) return E_POINTER;
*instance = new RssCOM();
if (NULL == *instance) return E_OUTOFMEMORY;
return S_OK;
}
STDMETHODIMP_(ULONG) RssCOM::AddRef(void)
{
return _ref.fetch_add( 1 );
}
STDMETHODIMP_(ULONG) RssCOM::Release(void)
{
if (0 == _ref.load() )
return _ref.load();
LONG r = _ref.fetch_sub( 1 );
if (0 == r)
delete(this);
return r;
}
STDMETHODIMP RssCOM::QueryInterface(REFIID riid, PVOID *ppvObject)
{
if (NULL == ppvObject) return E_POINTER;
if (IsEqualIID(riid, IID_IDispatch))
*ppvObject = static_cast<IDispatch*>(this);
else if (IsEqualIID(riid, IID_IUnknown))
*ppvObject = static_cast<IUnknown*>(this);
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
HRESULT RssCOM::OnSubscribe(WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, unsigned int FAR *puArgErr)
{
JSAPI_VERIFY_METHOD(wFlags);
JSAPI_VERIFY_PARAMCOUNT(pdispparams, 1);
BSTR url;
JSAPI_GETSTRING(url, pdispparams, 1, puArgErr);
SubscribeUrl(url, pvarResult);
return S_OK;
}
struct SubscribeThreadData
{
LPWSTR url;
};
static LPCWSTR FormatLangString(LPWSTR pszBuffer, INT cchBufferMax, LPWSTR pszFormat, INT cchFormatMax, INT formatId, ...)
{
HRESULT hr;
va_list argList;
va_start(argList, formatId);
if(NULL != WASABI_API_LNGSTRINGW_BUF(formatId, pszFormat, cchFormatMax))
{
hr = StringCchVPrintfExW(pszBuffer, cchBufferMax, NULL, NULL, STRSAFE_IGNORE_NULLS, pszFormat, argList);
}
else
{
hr = E_FAIL;
}
va_end(argList);
return (SUCCEEDED(hr)) ? pszBuffer : NULL;
}
static DWORD WINAPI SubscribeThreadProc(void *param)
{
SubscribeThreadData *data = (SubscribeThreadData *)param;
Channel newFeed;
newFeed.updateTime = updateTime;
newFeed.autoUpdate = autoUpdate;
newFeed.autoDownload = autoDownload;
newFeed.autoDownloadEpisodes = autoDownloadEpisodes;
newFeed.SetURL(data->url);
newFeed.needsRefresh = true;
WCHAR szBuffer1[2048] = {0}, szBuffer2[2048] = {0};
LPCWSTR pszMessage = NULL, pszTitle = NULL;
switch (DownloadFeedInformation(newFeed))
{
case DOWNLOAD_SUCCESS:
{
Nullsoft::Utility::AutoLock lock (channels LOCKNAME("AddURL"));
newFeed.autoDownload = ::autoDownload;
if (channels.AddChannel(newFeed))
cloud.Pulse();
}
break;
case DOWNLOAD_DUPLICATE:
pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_ALREADY_SUBSCRIBED, newFeed.title, data->url);
break;
case DOWNLOAD_404:
pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_FILE_NOT_FOUND, data->url);
break;
case DOWNLOAD_TIMEOUT:
pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_CONNECTION_TIMED_OUT, data->url);
break;
case DOWNLOAD_ERROR_PARSING_XML:
pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_ERROR_PARSING_XML, data->url);
break;
case DOWNLOAD_NOTRSS:
pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_INVALID_RSS_FEED, data->url);
break;
case DOWNLOAD_CONNECTIONRESET:
pszMessage = FormatLangString(szBuffer1, ARRAYSIZE(szBuffer1), szBuffer2, ARRAYSIZE(szBuffer2), IDS_CONNECTION_RESET, data->url);
break;
case DOWNLOAD_NOHTTP:
pszMessage = WASABI_API_LNGSTRINGW_BUF(IDS_NO_JNETLIB, szBuffer1, ARRAYSIZE(szBuffer1));
pszTitle = WASABI_API_LNGSTRINGW_BUF(IDS_JNETLIB_MISSING, szBuffer2, ARRAYSIZE(szBuffer2));
break;
case DOWNLOAD_NOPARSER:
pszMessage = WASABI_API_LNGSTRINGW_BUF(IDS_NO_EXPAT, szBuffer1, ARRAYSIZE(szBuffer1));
pszTitle = WASABI_API_LNGSTRINGW_BUF(IDS_EXPAT_MISSING, szBuffer2, ARRAYSIZE(szBuffer2));
break;
}
if(NULL != pszMessage)
{
if (NULL == pszTitle)
pszTitle = WASABI_API_LNGSTRINGW_BUF(IDS_ERROR_SUBSCRIBING_TO_PODCAST, szBuffer2, ARRAYSIZE(szBuffer2));
MessageBox(plugin.hwndLibraryParent, pszMessage, pszTitle, MB_ICONERROR | MB_OK);
}
Plugin_FreeString(data->url);
free(data);
return 0;
}
LPCWSTR RssCOM::GetName()
{
return L"Podcast";
}
HRESULT RssCOM::SubscribeUrl(BSTR url, VARIANT FAR *result)
{
HRESULT hr;
SubscribeThreadData *data = (SubscribeThreadData*)malloc(sizeof(SubscribeThreadData));
if (NULL != data)
{
data->url = Plugin_CopyString(url);
DWORD threadId;
HANDLE hThread = CreateThread(NULL, NULL, SubscribeThreadProc, (void*)data, NULL, &threadId);
if (NULL == hThread)
{
DWORD error = GetLastError();
hr = HRESULT_FROM_WIN32(error);
Plugin_FreeString(data->url);
free(data);
}
else
{
CloseHandle(hThread);
hr = S_OK;
}
}
else
hr = E_OUTOFMEMORY;
if (NULL != result)
{
VariantInit(result);
V_VT(result) = VT_BOOL;
V_BOOL(result) = (SUCCEEDED(hr) ? VARIANT_TRUE : VARIANT_FALSE);
}
return hr;
}

View File

@ -0,0 +1,42 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_RSS_COM_HEADER
#define NULLSOFT_PODCAST_PLUGIN_RSS_COM_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
#include <atomic>
#include "../nu/dispatchTable.h"
class RssCOM : public IDispatch
{
public:
typedef enum
{
DISPATCH_SUBSCRIBE = 0,
} DispatchCodes;
protected:
RssCOM();
~RssCOM();
public:
static HRESULT CreateInstance(RssCOM **instance);
static HRESULT SubscribeUrl(BSTR url, VARIANT FAR *result);
static LPCWSTR GetName();
/* IUnknown*/
STDMETHOD(QueryInterface)(REFIID riid, PVOID *ppvObject);
STDMETHOD_(ULONG, AddRef)(void);
STDMETHOD_(ULONG, Release)(void);
protected:
DISPTABLE_INCLUDE();
DISPHANDLER_REGISTER(OnSubscribe);
std::atomic<std::size_t> _ref = 1;
};
#endif //NULLSOFT_PODCAST_PLUGIN_RSS_COM_HEADER

View File

@ -0,0 +1,181 @@
#include "Main.h"
#include "RSSParse.h"
#include "RFCDate.h"
#include "../xml/XMLNode.h"
#include "Feeds.h"
#include "Defaults.h"
#include "ChannelSync.h"
#include "ParseUtil.h"
#include <strsafe.h>
static void ReadWinampSpecificItem(const XMLNode *item, RSS::Item &newItem)
{
newItem.listened = PropertyIsTrue(item, L"winamp:listened");
newItem.downloaded = PropertyIsTrue(item, L"winamp:downloaded");
}
static void ReadRSSItem(XMLNode *item, Channel &channel, bool doWinampSpecificTags)
{
RSS::MutableItem newItem;
if (doWinampSpecificTags)
ReadWinampSpecificItem(item, newItem);
const wchar_t *pubdate = GetContent(item, L"pubDate");
if (pubdate && pubdate[0])
{
newItem.publishDate = MakeRFCDate(pubdate);
if (newItem.publishDate <= 0)
{
newItem.publishDate = _time64(0);
newItem.generatedDate = true;
}
else
newItem.generatedDate = false;
}
else
{
newItem.publishDate = _time64(0);
newItem.generatedDate = true;
}
const wchar_t *itemName = GetContent(item, L"title");
if (itemName && itemName[0])
newItem.SetItemName(itemName);
else
{
wchar_t date_str[128] = {0};
MakeRFCDateString(newItem.publishDate, date_str, 128);
newItem.SetItemName(date_str);
}
newItem.SetLink(GetContent(item, L"link"));
newItem.SetURL(GetProperty(item, L"enclosure", L"url"));
const wchar_t* src = GetProperty(item, L"source", L"src");
if (!src || !src[0])
{
newItem.SetSourceURL(GetProperty(item, L"source", L"url"));
}
else
{
newItem.SetSourceURL(src/*GetProperty(item, L"source", L"src")*/);
}
newItem.SetSize(GetProperty(item, L"enclosure", L"length"));
const wchar_t *guid = GetContent(item, L"guid");
if (!guid || !guid[0])
guid = GetProperty(item, L"enclosure", L"url");
if (guid && guid[0])
{
newItem.SetGUID(guid);
}
else
{
wchar_t generated_guid[160] = {0};
StringCbPrintf(generated_guid, sizeof(generated_guid), L"%s%s", channel.title, newItem.itemName);
newItem.SetGUID(generated_guid);
}
const wchar_t *description = GetContent(item, L"description");
if (!description || !description[0])
description = GetContent(item, L"content:encoded");
newItem.SetDescription(description);
const wchar_t *duration = GetContent(item, L"itunes:duration");
if (duration && duration[0])
newItem.SetDuration(duration);
if (newItem.itemName && newItem.itemName[0])
{
channel.items.push_back(newItem);
}
}
void ReadWinampSpecificChannel(const XMLNode *node, Channel &newChannel)
{
const XMLNode *curNode = 0;
const wchar_t *lastupdate = node->GetProperty(L"winamp:lastupdate");
if (lastupdate && lastupdate[0])
newChannel.lastUpdate = MakeRFCDate(lastupdate);
const wchar_t *winamp_url = GetContent(node, L"winamp:url");
newChannel.SetURL(winamp_url);
// set to preference value first
newChannel.updateTime = updateTime;
newChannel.autoUpdate = autoUpdate;
newChannel.autoDownload = autoDownload;
newChannel.autoDownloadEpisodes = autoDownloadEpisodes;
curNode = node->Get(L"winamp:update");
if (curNode)
{
newChannel.useDefaultUpdate = PropertyIsTrue(curNode, L"usedefaultupdate");
newChannel.needsRefresh = PropertyIsTrue(curNode, L"needsrefresh");
if (!newChannel.useDefaultUpdate)
{
newChannel.updateTime = _wtoi64(curNode->GetProperty(L"updatetime"));
newChannel.autoUpdate = PropertyIsTrue(curNode, L"autoupdate");
}
}
curNode = node->Get(L"winamp:download");
if (curNode)
{
newChannel.autoDownload = PropertyIsTrue(curNode, L"autodownload");
if (newChannel.autoDownload)
{
const wchar_t *prop = curNode->GetProperty(L"autoDownloadEpisodes");
if (prop)
newChannel.autoDownloadEpisodes = _wtoi(prop);
}
}
}
static void ReadRSSChannel(const XMLNode *node, Channel &newChannel, bool doWinampSpecificTags)
{
XMLNode::NodeList::const_iterator itemItr;
if (doWinampSpecificTags)
ReadWinampSpecificChannel(node, newChannel);
const wchar_t *title = GetContent(node, L"title");
newChannel.SetTitle(title);
const wchar_t *link = GetContent(node, L"link");
newChannel.SetLink(link);
const wchar_t *description = GetContent(node, L"description");
newChannel.SetDescription(description);
const wchar_t *ttl = GetContent(node, L"ttl");
if (ttl)
newChannel.ttl = _wtoi(ttl);
const XMLNode::NodeList *items = node->GetList(L"item");
if (items)
{
for (itemItr = items->begin(); itemItr != items->end(); itemItr++)
ReadRSSItem(*itemItr, newChannel, doWinampSpecificTags);
}
}
void ReadRSS(const XMLNode *rss, ChannelSync *sync, bool doWinampSpecificTags, const wchar_t *url)
{
XMLNode::NodeList::const_iterator itr;
sync->BeginChannelSync();
const XMLNode::NodeList *channelList = rss->GetList(L"channel");
if (channelList)
{
for (itr = channelList->begin(); itr != channelList->end(); itr ++)
{
Channel newChannel;
ReadRSSChannel(*itr, newChannel, doWinampSpecificTags);
if (!newChannel.url || !newChannel.url[0])
newChannel.SetURL(url);
sync->NewChannel(newChannel);
}
}
sync->EndChannelSync();
}

View File

@ -0,0 +1,9 @@
#ifndef NULLSOFT_RSSPARSEH
#define NULLSOFT_RSSPARSEH
#include "../xml/XMLNode.h"
#include "ChannelSync.h"
void ReadRSS(const XMLNode *rss, ChannelSync *sync, bool doWinampSpecificTags, const wchar_t *url);
#endif

View File

@ -0,0 +1,51 @@
to fix
----
crash on shutdown
/////------------ 1.1 below --------------
automatically switch to 'custom' if you click on dropdown in "add" or "edit" url dialog
need icon for listened media
maybe one for read text?
strip whitespace from beginning of titles
multiple-select
Allow for customizing the download location in add/edit url
deletable items (needs to move to a separate 'deleted items' list so we don't re-add them next rss refresh)
drag-n-drop from webpages
once we get an HTTP 200, we should put the downloaded on the 'downloads' list, and be able to update the download percentage status as necessary
BACKGROUND DOWNLOADER
<<<
avoid multiple downloads of the same thing
avoid downlaoding things that have already been downloaded.
range / if-range to handle download resuming
save the last modified dates from "Last-Modified" header
save unfinished downloads to an XML file and read on load
>>>
UNIFIED DOWNLOAD MANAGER CONCEPT !!!!!
who needs updates
downloaded file list
downloads page (to refresh view)
item object
podcast page (to refresh view)
new way of listing items
---
create a common "items" data structure that select channels add their items to.
When a channel is select, it adds its items.
When a channel is deselected, it removes its items.
When a channel is refreshed, it re-adds its items (assuming the item-adder function protects against dupes)
only potential issue is if a channel somehow "loses" items (or an item's GUID is changes)
could be fixed by either 1) keeps track of "parent channel" in the items list
2) rebuilding the entire items list on every channel refresh
or 3) preventing GUID changes and item deletions (or forcing an item list rebuild if it does occur)

View File

@ -0,0 +1,57 @@
#include "main.h"
#include "api__ml_wire.h"
#include "UpdateAutoDownload.h"
int UpdateAutoDownload::episodes[] = {0, // AUTODOWNLOAD_NEVER
1, // AUTODOWNLOAD_LAST_ONE
2, // AUTODOWNLOAD_LAST_TWO
3, // AUTODOWNLOAD_LAST_THREE
5, // AUTODOWNLOAD_LAST_FIVE
};
const wchar_t *UpdateAutoDownload::GetTitle(int position, wchar_t *buffer, int bufferMax)
{
if (NULL == buffer)
return NULL;
INT stringId = IDS_ERROR_FYEO;
switch (position)
{
case AUTODOWNLOAD_NEVER: stringId = IDS_ATD_NEVER; break;
case AUTODOWNLOAD_LAST_ONE: stringId = IDS_ATD_LASTONE; break;
case AUTODOWNLOAD_LAST_TWO: stringId = IDS_ATD_LASTTWO; break;
case AUTODOWNLOAD_LAST_THREE: stringId = IDS_ATD_LASTTHREE; break;
case AUTODOWNLOAD_LAST_FIVE: stringId = IDS_ATD_LASTFIVE; break;
}
return WASABI_API_LNGSTRINGW_BUF(stringId, buffer, bufferMax);
}
bool UpdateAutoDownload::GetAutoDownload(int selection)
{
if (selection == AUTODOWNLOAD_NEVER)
return false;
else
return true;
}
int UpdateAutoDownload::GetAutoDownloadEpisodes(int selection)
{
if (selection >= 0 && selection < AUTODOWNLOAD_NUMENTRIES)
return episodes[selection];
else
return 0;
}
int UpdateAutoDownload::GetSelection(int selEpisodes, bool autoDownload)
{
if (!autoDownload)
return AUTODOWNLOAD_NEVER;
for (int i = AUTODOWNLOAD_LAST_ONE;i < AUTODOWNLOAD_NUMENTRIES;i++)
if (selEpisodes == episodes[i])
return i;
return AUTODOWNLOAD_LAST_ONE;
}

View File

@ -0,0 +1,28 @@
#ifndef NULLSOFT_UPDATEAUTODOWNLOADH
#define NULLSOFT_UPDATEAUTODOWNLOADH
/* This header file is used by FeedsDialog.h
It provides a set of helper functions to deal with the combo box for auto download
basically - converts between combo box choice and int
*/
namespace UpdateAutoDownload
{
enum
{
AUTODOWNLOAD_NEVER = 0,
AUTODOWNLOAD_LAST_ONE,
AUTODOWNLOAD_LAST_TWO,
AUTODOWNLOAD_LAST_THREE,
AUTODOWNLOAD_LAST_FIVE,
AUTODOWNLOAD_NUMENTRIES
};
extern int episodes[];
const wchar_t *GetTitle(int position, wchar_t *buffer, int bufferMax);
bool GetAutoDownload(int selection);
int GetAutoDownloadEpisodes(int selection);
int GetSelection(int selEpisodes, bool autoDownload);
}
#endif

View File

@ -0,0 +1,62 @@
#include "main.h"
#include "api__ml_wire.h"
#include "UpdateTime.h"
__time64_t Update::times[] = {0, // TIME_MANUALLY
60 /* 1 minute */ * 60 /* 1 hour */ * 24 /* 1 day */ * 7 /* 1 week */, // TIME_WEEKLY
60 /* 1 minute */ * 60 /* 1 hour */ * 24 /* 1 day */, // TIME_DAILY
60 /* 1 minute */ * 60 /* 1 hour */, // TIME_HOURLY
};
const wchar_t *Update::GetTitle( int position, wchar_t *buffer, int bufferMax )
{
if ( NULL == buffer )
return NULL;
INT stringId = IDS_ERROR_FYEO;
switch ( position )
{
case TIME_MANUALLY:
stringId = IDS_UPD_MANUALLY;
break;
case TIME_WEEKLY:
stringId = IDS_UPD_WEEK;
break;
case TIME_DAILY:
stringId = IDS_UPD_DAY;
break;
case TIME_HOURLY:
stringId = IDS_UPD_HOUR;
break;
}
return WASABI_API_LNGSTRINGW_BUF( stringId, buffer, bufferMax );
}
bool Update::GetAutoUpdate(int selection)
{
if (selection == TIME_MANUALLY)
return false;
else
return true;
}
__time64_t Update::GetTime(int selection)
{
if (selection >= 0 && selection < TIME_NUMENTRIES)
return times[selection];
else
return 0;
}
int Update::GetSelection(__time64_t selTime, bool autoUpdate)
{
if (!autoUpdate)
return TIME_MANUALLY;
for (int i = TIME_WEEKLY;i < TIME_NUMENTRIES;i++)
if (selTime >= times[i])
return i;
return TIME_DAILY;
}

View File

@ -0,0 +1,29 @@
#ifndef NULLSOFT_UPDATETIMEH
#define NULLSOFT_UPDATETIMEH
#include <time.h>
/* This header file is used by FeedsDialog.h
It provides a set of helper functions to deal with the combo box for update time
basically - converts between combo box choice and __time64_t
*/
namespace Update
{
enum
{
TIME_MANUALLY = 0,
TIME_WEEKLY,
TIME_DAILY,
TIME_HOURLY,
TIME_NUMENTRIES
};
extern __time64_t times[];
const wchar_t *GetTitle(int position, wchar_t *buffer, int bufferMax);
bool GetAutoUpdate(int selection);
__time64_t GetTime(int selection);
int GetSelection(__time64_t selTime, bool autoUpdate);
}
#endif

View File

@ -0,0 +1,69 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
#define NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
#include "../nu/trace.h"
#define CSTR_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
#ifndef ARRAYSIZE
#define ARRAYSIZE(blah) (sizeof(blah)/sizeof(*blah))
#endif
#ifndef LONGX86
#ifdef _WIN64
#define LONGX86 LONG_PTR
#else /*_WIN64*/
#define LONGX86 LONG
#endif /*_WIN64*/
#endif // LONGX86
#ifdef __cplusplus
#define SENDMSG(__hwnd, __msgId, __wParam, __lParam) ::SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
#else
#define SENDMSG(__hwnd, __msgId, __wParam, __lParam) SendMessageW((__hwnd), (__msgId), (__wParam), (__lParam))
#endif // __cplusplus
#define SENDMLIPC(__hwndML, __ipcMsgId, __param) SENDMSG((__hwndML), WM_ML_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
#define SENDWAIPC(__hwndWA, __ipcMsgId, __param) SENDMSG((__hwndWA), WM_WA_IPC, (WPARAM)(__param), (LPARAM)(__ipcMsgId))
#if defined (_WIN64)
#define MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWLP_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
#else
#define MSGRESULT(__hwnd, __result) { SetWindowLongPtrW((__hwnd), DWL_MSGRESULT, ((LONGX86)(LONG_PTR)(__result))); return TRUE; }
#endif
#define SENDCMD(__hwnd, __ctrlId, __eventId, __hctrl) (SENDMSG((__hwnd), WM_COMMAND, MAKEWPARAM(__ctrlId, __eventId), (LPARAM)(__hctrl)))
LPWSTR Plugin_MallocString(size_t cchLen);
LPWSTR Plugin_ReAllocString(LPWSTR pszString, size_t cchLen);
void Plugin_FreeString(LPWSTR pszString);
LPWSTR Plugin_CopyString(LPCWSTR pszSource);
LPSTR Plugin_MallocAnsiString(size_t cchLen);
LPSTR Plugin_CopyAnsiString(LPCSTR pszSource);
void Plugin_FreeAnsiString(LPSTR pszString);
LPWSTR Plugin_DuplicateResString(LPCWSTR pszResource);
void Plugin_FreeResString(LPWSTR pszResource);
HRESULT Plugin_CopyResString(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszString);
LPSTR Plugin_WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWSTR lpWideCharStr, INT cchWideChar, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
LPWSTR Plugin_MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCSTR lpMultiByteStr, INT cbMultiByte);
void Plugin_SafeRelease(IUnknown *pUnk);
HRESULT Plugin_FileExtensionFromUrl(LPWSTR pszBuffer, INT cchBufferMax, LPCWSTR pszUrl, LPCWSTR defaultExtension);
void Plugin_ReplaceBadPathChars(LPWSTR pszPath);
INT Plugin_CleanDirectory(LPWSTR pszPath);
HRESULT Plugin_EnsurePathExist(LPCWSTR pszDirectory);
#endif // NULLSOFT_PODCAST_PLUGIN_UTIL_HEADER

View File

@ -0,0 +1,14 @@
#ifndef NULLSOFT_WANTSDOWNLOADSTATUSH
#define NULLSOFT_WANTSDOWNLOADSTATUSH
class WantsDownloadStatus
{
public:
virtual void DownloadStarted() {}
virtual void UpdateDownloadStatus(size_t downloaded, size_t totalBytes) {}
virtual void DownloadDone() {}
virtual void DownloadFailed() {}
};
#endif

View File

@ -0,0 +1,99 @@
#include "Main.h"
#include "./subscriptionView.h"
#include "FeedsDialog.h"
#include "Feeds.h"
#include <algorithm>
ChannelList channels;
//CategoryIndex sourceByCategory;
using namespace Nullsoft::Utility;
//LockGuard /*feedGuard, */channelGuard, categoryGuard;
void WireManager::BeginChannelSync()
{
// TODO something better than this =)
// like making 'touched' flags and delete untouched ones
//sources.clear();
//sourceByCategory.clear();
}
void WireManager::NewChannel(const Channel &newChannel)
{
AutoLock lock (channels LOCKNAME("NewChannel"));
for (ChannelList::iterator itr=channels.begin();itr!=channels.end(); itr++)
{
if (*itr == newChannel)
{
itr->UpdateFrom(newChannel);
return;
}
}
channels.push_back(newChannel);
}
void WireManager::EndChannelSync()
{
//SubscriptionView_RefreshChannels(NULL, TRUE);
}
bool ChannelTitleSort(Channel &channel1, Channel &channel2)
{
return (CSTR_LESS_THAN == CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, channel1.title, -1, channel2.title, -1));
}
void ChannelList::SortByTitle()
{
AutoLock lock (channelGuard LOCKNAME("SortByTitle"));
std::sort(channelList.begin(), channelList.end(), ChannelTitleSort);
}
void ChannelList::push_back(const Channel &channel)
{
channelList.push_back(channel);
}
bool ChannelList::AddChannel(Channel &channel)
{
ChannelList::iterator found;
for (found=channels.begin(); found!=channels.end(); found++)
{
if (!wcscmp(found->url, channel.url))
break;
}
if (found == channels.end()){
channel.needsRefresh=true;
push_back(channel);
SaveChannels(*this);
return true;
}
return false;
}
size_t ChannelList::GetNumPodcasts()
{
return channels.size();
}
ifc_podcast *ChannelList::EnumPodcast(size_t i)
{
if (i < channels.size())
return &channels[i];
else
return 0;
}
#undef CBCLASS
#define CBCLASS ChannelList
START_DISPATCH;
CB(API_PODCASTS_GETNUMPODCASTS, GetNumPodcasts)
CB(API_PODCASTS_ENUMPODCAST, EnumPodcast)
END_DISPATCH;
#undef CBCLASS

View File

@ -0,0 +1,62 @@
#ifndef NULLSOFT_WIREH
#define NULLSOFT_WIREH
#include "Feeds.h"
#include "../nu/AutoLock.h"
#include "api_podcasts.h"
#include <vector>
class ChannelList : public api_podcasts
{
public:
ChannelList() : channelGuard( "Feed Guard" ) {}
typedef std::vector<Channel> ChannelContainer;
typedef ChannelContainer::iterator iterator;
size_t size() { return channelList.size(); }
bool empty() { return channelList.empty(); }
void push_back( const Channel &channel );
bool AddChannel( Channel &channel );
iterator begin() { return channelList.begin(); }
iterator end() { return channelList.end(); }
void RemoveChannel( int index ) { channelList.erase( channelList.begin() + index ); }
Channel &operator[]( size_t index ) { return channelList[ index ]; }
ChannelContainer channelList;
operator Nullsoft::Utility::LockGuard &( ) { return channelGuard; }
void SortByTitle();
Nullsoft::Utility::LockGuard channelGuard;
public: // api_podcasts interface
size_t GetNumPodcasts();
ifc_podcast *EnumPodcast( size_t i );
protected:
RECVS_DISPATCH;
};
extern ChannelList channels;
//extern CategoryIndex sourceByCategory;
#include "ChannelSync.h"
class WireManager : public ChannelSync
{
public:
void BeginChannelSync();
void NewChannel( const Channel &newChannel );
void EndChannelSync();
};
extern WireManager channelMgr;
#endif

View File

@ -0,0 +1,272 @@
#include "Main.h"
#include "Feeds.h"
#include "RFCDate.h"
#include "Downloaded.h"
#include "../nu/AutoLock.h"
#include "Defaults.h"
#include "../Agave/Language/api_language.h"
using namespace Nullsoft::Utility;
static void XMLWriteString(FILE *fp, const wchar_t *str)
{
for (const wchar_t *itr = str; *itr; itr=CharNextW(itr))
{
switch (*itr)
{
case '<': fputws(L"&lt;", fp); break;
case '>': fputws(L"&gt;", fp); break;
case '&': fputws(L"&amp;", fp); break;
case '\"': fputws(L"&quot;", fp); break;
case '\'': fputws(L"&apos;", fp); break;
default: fputwc(*itr, fp); break;
}
}
}
static void InstaProp(FILE *fp, const wchar_t *property, const wchar_t *value)
{
fwprintf(fp, L" %s=\"", property);
XMLWriteString(fp, value);
fputws(L"\"", fp);
}
static void InstaProp(FILE *fp, const wchar_t *property, int value)
{
fwprintf(fp, L" %s=\"%i\"", property, value);
}
static void InstaPropI64(FILE *fp, const wchar_t *property, int64_t value)
{
fwprintf(fp, L" %s=\"%I64d\"", property, value);
}
static void InstaPropTime(FILE *fp, const wchar_t *property, __time64_t value)
{
fwprintf(fp, L" %s=\"%I64u\"", property, value);
}
static void InstaProp(FILE *fp, const wchar_t *property, float value)
{
_fwprintf_l(fp, L" %s=\"%3.3f\"", WASABI_API_LNG->Get_C_NumericLocale(), property, value);
}
static void InstaProp(FILE *fp, const wchar_t *property, bool value)
{
fwprintf(fp, L" %s=\"", property);
if (value)
fputws(L"true\"", fp);
else
fputws(L"false\"", fp);
}
static void InstaTag(FILE *fp, const wchar_t *tag, const wchar_t *content)
{
if (content && !((INT_PTR)content < 65536) && *content)
{
fwprintf(fp, L"<%s>", tag);
XMLWriteString(fp, content);
fwprintf(fp, L"</%s>\r\n", tag);
}
}
static void InstaTag(FILE *fp, const wchar_t *tag, unsigned int uint)
{
fwprintf(fp, L"<%s>%u</%s>", tag, uint, tag);
}
static void InstaTag(FILE *fp, const wchar_t *tag, __time64_t uint)
{
fwprintf(fp, L"<%s>%I64u</%s>", tag, uint, tag);
}
static void BuildXMLString(FILE *fp, const RSS::Item &item)
{
fputws(L"<item", fp);
InstaProp(fp, L"winamp:listened", item.listened);
InstaProp(fp, L"winamp:downloaded", item.downloaded);
fputws(L">\r\n", fp);
InstaTag(fp, L"title", item.itemName);
if (item.url && item.url[0])
{
fputws(L"<enclosure", fp);
InstaProp(fp, L"url", item.url);
InstaPropI64(fp, L"length", item.size);
fputws(L"/>\r\n", fp);
}
InstaTag(fp, L"guid", item.guid);
InstaTag(fp, L"description", item.description);
InstaTag(fp, L"link", item.link);
if (item.duration && item.duration[0])
{
InstaTag(fp, L"itunes:duration", item.duration);
}
if (item.publishDate)
{
wchar_t date_str[128] = {0};
MakeRFCDateString(item.publishDate, date_str, 128);
InstaTag(fp, L"pubDate", date_str);
}
if (item.sourceUrl && item.sourceUrl[0])
{
fputws(L"<source",fp);
InstaProp(fp, L"src", item.sourceUrl);
fputws(L"/>\r\n", fp);
}
fputws(L"</item>\r\n", fp);
}
static void BuildXMLString(FILE *fp, Channel &channel)
{
fputws(L"<channel", fp);
wchar_t date_str[128] = {0};
MakeRFCDateString(channel.lastUpdate, date_str, 128);
InstaProp(fp, L"winamp:lastupdate", date_str);
fputws(L">\r\n", fp);
// write update settings for this channel
fputws(L"<winamp:update", fp);
InstaProp(fp, L"usedefaultupdate", channel.useDefaultUpdate);
InstaProp(fp, L"needsrefresh", channel.needsRefresh);
if (!channel.useDefaultUpdate)
{
InstaProp(fp, L"autoupdate", channel.autoUpdate);
InstaPropTime(fp, L"updatetime", channel.updateTime);
}
fputws(L"/>\r\n", fp);
if (!channel.useDefaultUpdate)
{
fputws(L"<winamp:download", fp);
InstaProp(fp, L"autodownload", channel.autoDownload);
if (channel.autoDownload)
{
InstaProp(fp, L"autoDownloadEpisodes", channel.autoDownloadEpisodes);
}
fputws(L"/>\r\n", fp);
}
InstaTag(fp, L"winamp:url", channel.url);
InstaTag(fp, L"title", channel.title);
InstaTag(fp, L"link", channel.link);
InstaTag(fp, L"description", channel.description);
if (channel.ttl)
InstaTag(fp, L"ttl", channel.ttl);
fputws(L"\r\n", fp);
Channel::ItemList::iterator itemItr;
for (itemItr = channel.items.begin();itemItr != channel.items.end(); itemItr++)
BuildXMLString(fp, *itemItr);
fputws(L"</channel>\r\n", fp);
}
void SaveChannels(const wchar_t *fileName, ChannelList &channels)
{
FILE *fp = _wfopen(fileName, L"wb");
if (fp)
{
ChannelList::iterator itr;
wchar_t BOM = 0xFEFF;
fwrite(&BOM, sizeof(BOM), 1, fp);
fputws(L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n", fp);
fputws(L"<rss version=\"2.0\" xmlns:winamp=\"http://www.winamp.com\">\r\n", fp);
for (itr = channels.begin();itr != channels.end();itr++)
BuildXMLString(fp, *itr);
fputws(L"</rss>", fp);
fclose(fp);
}
}
static void BuildXMLPreferences(FILE *fp)
{
fputws(L"<download", fp);
InstaProp(fp, L"downloadpath", defaultDownloadPath);
InstaProp(fp, L"autodownload", autoDownload);
InstaProp(fp, L"autoDownloadEpisodes", autoDownloadEpisodes);
InstaProp(fp, L"needToMakePodcastsView", needToMakePodcastsView);
fputws(L"/>\r\n", fp);
fputws(L"<update", fp);
InstaPropTime(fp, L"updatetime", updateTime);
InstaProp(fp, L"autoupdate", autoUpdate);
InstaProp(fp, L"updateonlaunch", updateOnLaunch);
fputws(L"/>\r\n", fp);
fputws(L"<subscriptions", fp);
InstaProp(fp, L"htmldivider", htmlDividerPercent);
InstaProp(fp, L"channeldivider", channelDividerPercent);
InstaProp(fp, L"itemtitlewidth", itemTitleWidth);
InstaProp(fp, L"itemdatewidth", itemDateWidth);
InstaProp(fp, L"itemmediawidth", itemMediaWidth);
InstaProp(fp, L"itemsizewidth", itemSizeWidth);
InstaProp(fp, L"currentitemsort", currentItemSort);
InstaProp(fp, L"itemsortascending", itemSortAscending);
InstaProp(fp, L"channelsortascending", channelSortAscending);
InstaProp(fp, L"channelLastSelection", channelLastSelection);
fputws(L"/>\r\n", fp);
fputws(L"<downloadsView", fp);
InstaProp(fp, L"downloadsChannelWidth", downloadsChannelWidth);
InstaProp(fp, L"downloadsItemWidth", downloadsItemWidth);
InstaProp(fp, L"downloadsProgressWidth", downloadsProgressWidth);
InstaProp(fp, L"downloadsPathWidth", downloadsPathWidth);
InstaProp(fp, L"downloadsItemSort", downloadsItemSort);
InstaProp(fp, L"downloadsSortAscending", downloadsSortAscending);
fputws(L"/>\r\n", fp);
fputws(L"<service", fp);
InstaProp(fp, L"url", serviceUrl);
fputws(L"/>\r\n", fp);
}
static void BuildXMLDownloads(FILE *fp, DownloadList &downloads)
{
fputws(L"<downloads>", fp);
AutoLock lock (downloads);
DownloadList::const_iterator itr;
for ( itr = downloads.begin(); itr != downloads.end(); itr++ )
{
fputws(L"<download>", fp);
InstaTag(fp, L"channel", itr->channel);
InstaTag(fp, L"item", itr->item);
InstaTag(fp, L"url", itr->url);
InstaTag(fp, L"path", itr->path);
InstaTag(fp, L"publishDate", itr->publishDate);
fputws(L"</download>\r\n", fp);
}
fputws(L"</downloads>", fp);
}
void SaveSettings(const wchar_t *fileName, DownloadList &downloads)
{
FILE *fp = _wfopen(fileName, L"wb");
if (fp)
{
wchar_t BOM = 0xFEFF;
fwrite(&BOM, sizeof(BOM), 1, fp);
fputws(L"<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n", fp);
fputws(L"<winamp:preferences xmlns:winamp=\"http://www.winamp.com\" version=\"2\">\r\n", fp);
BuildXMLPreferences(fp);
BuildXMLDownloads(fp, downloads);
fputws(L"</winamp:preferences>", fp);
fclose(fp);
}
}

View File

@ -0,0 +1,8 @@
#ifndef NULLSOFT_XMLWRITERH
#define NULLSOFT_XMLWRITERH
#include "Wire.h"
#include "Downloaded.h"
void SaveChannels(const wchar_t *fileName, ChannelList &channels);
void SaveSettings(const wchar_t *fileName, DownloadList &downloads);
#endif

View File

@ -0,0 +1,27 @@
#ifndef NULLSOFT_ML_WIRE_API_H
#define NULLSOFT_ML_WIRE_API_H
#include "../Winamp/JSAPI2_api_security.h"
extern JSAPI2::api_security *jsapi2_security;
#define AGAVE_API_JSAPI2_SECURITY jsapi2_security
#include "../Agave/Language/api_language.h"
#include <api/application/api_application.h>
#define WASABI_API_APP applicationApi
#include "../omBrowser/obj_ombrowser.h"
extern obj_ombrowser *browserManager;
#define OMBROWSERMNGR browserManager
#include "../Winamp/api_stats.h"
extern api_stats *statsApi;
#define AGAVE_API_STATS statsApi
#include "../nu/threadpool/api_threadpool.h"
extern api_threadpool *threadPoolApi;
#define WASABI_API_THREADPOOL threadPoolApi
#include "../Agave/ExplorerFindFile/api_explorerfindfile.h"
#endif // !NULLSOFT_ML_WIRE_API_H

View File

@ -0,0 +1,46 @@
#ifndef NULLSOFT_API_ML_WIRE_PODCASTS_H
#define NULLSOFT_API_ML_WIRE_PODCASTS_H
#include <bfc/dispatch.h>
class ifc_podcastsubscribecallback;
class ifc_podcast;
class api_podcasts : public Dispatchable
{
protected:
api_podcasts() {}
~api_podcasts() {}
public:
size_t GetNumPodcasts();
ifc_podcast *EnumPodcast( size_t i );
// TODO: locking mechanism like api_playlists
// TODO: Remove playlists
// TODO: int Subscribe(const wchar_t *url, ifc_podcastsubscribecallback *callback);
/* TODO: method to download/parse a podcast channel w/o adding it to the list
maybe as part of a separate class? */
enum
{
API_PODCASTS_GETNUMPODCASTS = 0,
API_PODCASTS_ENUMPODCAST = 1,
};
};
inline size_t api_podcasts::GetNumPodcasts()
{
return _call( API_PODCASTS_GETNUMPODCASTS, (size_t)0 );
}
inline ifc_podcast *api_podcasts::EnumPodcast( size_t i )
{
return _call( API_PODCASTS_ENUMPODCAST, (ifc_podcast *)0, i );
}
// {4D2E9987-D955-45f0-A06F-371405E8B961}
static const GUID api_podcastsGUID =
{ 0x4d2e9987, 0xd955, 0x45f0, { 0xa0, 0x6f, 0x37, 0x14, 0x5, 0xe8, 0xb9, 0x61 } };
#endif // !NULLSOFT_API_ML_WIRE_PODCASTS_H

View File

@ -0,0 +1,687 @@
#include "main.h"
#include "./util.h"
#include "./channelEditor.h"
#include "api__ml_wire.h"
#include "./defaults.h"
#include "./updateTime.h"
#include "./UpdateAutoDownload.h"
#include "./errors.h"
#include "./feeds.h"
#include "./feedUtil.h"
#include "./cloud.h"
#include "./subscriptionView.h"
#include <strsafe.h>
using namespace Nullsoft::Utility;
extern Cloud cloud;
typedef struct __CHANNELEDITOR
{
HWND hOwner;
size_t iChannel;
UINT flags;
BOOL enableAutoDownload;
BOOL enableAutoUpdate;
__time64_t updateTime;
INT numOfAutoDownloadEpisodes;
} CHANNELEDITOR;
#define GetEditor(__hwnd) ((CHANNELEDITOR*)GetPropW((__hwnd), MAKEINTATOM(VIEWPROP)))
static INT_PTR CALLBACK ChannelEditor_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR ChannelEditor_Show(HWND hOwner, size_t channelIndex, UINT flags)
{
if (0 == VIEWPROP)
return 0;
if (NULL == hOwner)
hOwner = plugin.hwndLibraryParent;
if (0 == (CEF_CREATENEW & flags) && BAD_CHANNEL == channelIndex)
return 0;
CHANNELEDITOR editor;
ZeroMemory(&editor, sizeof(editor));
editor.hOwner = hOwner;
editor.iChannel = channelIndex;
editor.flags = flags;
INT_PTR result = WASABI_API_DIALOGBOXPARAMW(IDD_ADDURL, hOwner, ChannelEditor_DialogProc, (LPARAM)&editor);
return result;
}
static BOOL ChannelEditor_SetUpdateTime(HWND hwnd, __time64_t updateTime, BOOL enableAutoUpdate)
{
HWND hCombo = GetDlgItem(hwnd, IDC_UPDATELIST);
if (NULL == hCombo) return FALSE;
INT selectedVal = Update::GetSelection(updateTime, (FALSE != enableAutoUpdate));
INT iCount = (INT)SNDMSG(hCombo, CB_GETCOUNT, 0, 0L);
INT iSelect = CB_ERR;
for (INT i = 0; i < iCount && CB_ERR == iSelect; i++)
{
if (selectedVal == (INT)SNDMSG(hCombo, CB_GETITEMDATA, i, 0L))
iSelect = i;
}
if (CB_ERR == iSelect)
return FALSE;
if (iSelect != (INT)SNDMSG(hCombo, CB_GETCURSEL, 0, 0L) &&
CB_ERR == SNDMSG(hCombo, CB_SETCURSEL, iSelect, 0))
{
return FALSE;
}
return TRUE;
}
static BOOL ChannelEditor_SetAutoDownload(HWND hwnd, INT numOfEpisodes, BOOL enableAutoDownload)
{
HWND hCombo = GetDlgItem(hwnd, IDC_AUTODOWNLOADLIST);
if (NULL == hCombo) return FALSE;
INT selectedVal = UpdateAutoDownload::GetSelection(numOfEpisodes, (FALSE != enableAutoDownload));
INT iCount = (INT)SNDMSG(hCombo, CB_GETCOUNT, 0, 0L);
INT iSelect = CB_ERR;
for (INT i = 0; i < iCount && CB_ERR == iSelect; i++)
{
if (selectedVal == (INT)SNDMSG(hCombo, CB_GETITEMDATA, i, 0L))
iSelect = i;
}
if (CB_ERR == iSelect)
return FALSE;
if (iSelect != (INT)SNDMSG(hCombo, CB_GETCURSEL, 0, 0L) &&
CB_ERR == SNDMSG(hCombo, CB_SETCURSEL, iSelect, 0))
{
return FALSE;
}
return TRUE;
}
static BOOL ChannelEditor_InitCombobox(HWND hwnd)
{
HWND hCombo = GetDlgItem(hwnd, IDC_UPDATELIST);
HWND hAutoDownloadCombo = GetDlgItem(hwnd, IDC_AUTODOWNLOADLIST);
if ( NULL == hCombo || NULL == hAutoDownloadCombo ) return FALSE;
WCHAR szBuffer[256] = {0};
INT iPos = 0;
SNDMSG(hCombo, WM_SETREDRAW, FALSE, 0L);
for (INT i = 0; i < Update::TIME_NUMENTRIES; i++)
{
if (NULL != Update::GetTitle(i, szBuffer, ARRAYSIZE(szBuffer)))
{
iPos = (INT)SNDMSG(hCombo, CB_ADDSTRING, 0, (LPARAM)szBuffer);
if (CB_ERR != iPos)
{
SNDMSG(hCombo, CB_SETITEMDATA, (WPARAM)iPos, (LPARAM)i);
}
}
}
SNDMSG(hCombo, WM_SETREDRAW, TRUE, 0L);
SNDMSG(hAutoDownloadCombo, WM_SETREDRAW, FALSE, 0L);
for (INT i = 0; i < UpdateAutoDownload::AUTODOWNLOAD_NUMENTRIES; i++)
{
if (NULL != UpdateAutoDownload::GetTitle(i, szBuffer, ARRAYSIZE(szBuffer)))
{
iPos = (INT)SNDMSG(hAutoDownloadCombo, CB_ADDSTRING, 0, (LPARAM)szBuffer);
if (CB_ERR != iPos)
{
SNDMSG(hAutoDownloadCombo, CB_SETITEMDATA, (WPARAM)iPos, (LPARAM)i);
}
}
}
SNDMSG(hAutoDownloadCombo, WM_SETREDRAW, TRUE, 0L);
return TRUE;
}
static BOOL ChannelEditor_EnableUserSettings(HWND hwnd, BOOL fEnable)
{
BOOL result;
UINT activeButton = (FALSE == fEnable) ? IDC_USEDEFAULTS : IDC_USECUSTOM;
result = CheckRadioButton(hwnd, IDC_USEDEFAULTS, IDC_USECUSTOM, activeButton);
if (FALSE != result)
{
SendDlgItemMessage(hwnd, activeButton, BM_CLICK, 0, 0L);
}
return result;
}
#if 0
static BOOL ChannelEditor_EnableAutoDownload(HWND hwnd, BOOL fEnable)
{
BOOL result;
result = CheckDlgButton(hwnd, IDC_AUTODOWNLOAD, (FALSE != fEnable) ? BST_CHECKED : BST_UNCHECKED);
return result;
}
#endif
static BOOL ChannelEditor_SetUrl(HWND hwnd, LPCWSTR pszUrl)
{
HWND hEdit = GetDlgItem(hwnd, IDC_EDITURL);
if (NULL == hEdit) return FALSE;
return SNDMSG(hEdit, WM_SETTEXT, 0, (LPARAM)pszUrl);
}
static void ChannelEditor_UpdateTitle(HWND hwnd, BOOL fModified)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return;
if (0 != (CEF_CREATENEW & editor->flags))
return;
WCHAR szBuffer[256] = {0};
size_t remaining = ARRAYSIZE(szBuffer);
LPWSTR cursor = szBuffer;
if (FALSE != fModified)
{
StringCchCopyEx(cursor, remaining, L"*", &cursor, &remaining, 0);
}
WASABI_API_LNGSTRINGW_BUF(IDS_EDIT_CHANNEL, cursor, remaining);
INT cchLen = lstrlen(cursor);
cursor += cchLen;
remaining -= cchLen;
AutoLock lock (channels);
LPCWSTR title = NULL;
if (editor->iChannel < channels.size())
{
Channel *channel = &channels[editor->iChannel];
title = channel->title;
if (NULL != title && L'\0' != title)
{
StringCchPrintf(cursor, remaining, L": %s", title);
}
}
SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)szBuffer);
}
static void ChannelEditor_UpdateModifiedState(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return;
if (0 != (CEF_CREATENEW & editor->flags))
return;
AutoLock lock (channels);
if (editor->iChannel >= channels.size())
return;
BOOL fModified = FALSE;
Channel *channel = &channels[editor->iChannel];
HWND hControl;
if (FALSE == fModified && (NULL != (hControl = GetDlgItem(hwnd, IDC_USECUSTOM))) &&
(BST_CHECKED == SNDMSG(hControl, BM_GETCHECK, 0, 0L)) != (false == channel->useDefaultUpdate))
{
fModified = TRUE;
}
if (false == channel->useDefaultUpdate)
{
if (editor->enableAutoDownload != (BOOL)channel->autoDownload ||
editor->enableAutoUpdate != (BOOL)channel->autoUpdate ||
editor->updateTime != channel->updateTime ||
editor->numOfAutoDownloadEpisodes != channel->autoDownloadEpisodes)
{
fModified = TRUE;
}
}
hControl = GetDlgItem(hwnd, IDOK);
if (NULL != hControl) EnableWindow(hControl, fModified);
ChannelEditor_UpdateTitle(hwnd, fModified);
}
static BOOL ChannelEditor_Reposition(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return FALSE;
RECT rectEditor, rectOwner;
if (0 != (CEF_CENTEROWNER & editor->flags))
{
if (FALSE == GetWindowRect(editor->hOwner, &rectOwner) ||
FALSE == GetWindowRect(hwnd, &rectEditor))
{
return FALSE;
}
LONG x = rectOwner.left + ((rectOwner.right - rectOwner.left) - (rectEditor.right - rectEditor.left))/2;
LONG y = rectOwner.top + ((rectOwner.bottom - rectOwner.top) - (rectEditor.bottom - rectEditor.top))/2;
SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
SendMessage(hwnd, DM_REPOSITION, 0, 0L);
}
return TRUE;
}
static INT_PTR ChannelEditor_OnInitDialog(HWND hwnd, HWND hFocus, LPARAM lParam)
{
CHANNELEDITOR *editor = (CHANNELEDITOR*)lParam;
if (NULL == editor || FALSE == SetProp(hwnd, MAKEINTATOM(VIEWPROP), editor))
{
EndDialog(hwnd, 0);
return 0;
}
ChannelEditor_InitCombobox(hwnd);
editor->enableAutoDownload = autoDownload;
editor->enableAutoUpdate = autoUpdate;
editor->updateTime = updateTime;
editor->numOfAutoDownloadEpisodes = autoDownloadEpisodes;
if (0 != (CEF_CREATENEW & editor->flags))
{
ChannelEditor_SetUrl(hwnd, L"");
ChannelEditor_EnableUserSettings(hwnd, FALSE);
}
else
{
AutoLock lock (channels);
if (editor->iChannel >= channels.size())
{
EndDialog(hwnd, 0);
return 0;
}
Channel *channel = &channels[editor->iChannel];
HWND hControl;
hControl = GetDlgItem(hwnd, IDC_EDITURL);
if (NULL != hControl) SNDMSG(hControl, EM_SETREADONLY, TRUE, 0L);
hControl = GetDlgItem(hwnd, IDOK);
if (NULL != hControl)
{
WCHAR szBuffer[128] = {0};
WASABI_API_LNGSTRINGW_BUF(IDS_SAVE, szBuffer, ARRAYSIZE(szBuffer));
SNDMSG(hControl, WM_SETTEXT, 0, (LPARAM)szBuffer);
}
if (false == channel->useDefaultUpdate)
{
editor->enableAutoDownload = channel->autoDownload;
editor->enableAutoUpdate = channel->autoUpdate;
editor->updateTime = channel->updateTime;
editor->numOfAutoDownloadEpisodes = channel->autoDownloadEpisodes;
}
ChannelEditor_SetUrl(hwnd, channel->url);
ChannelEditor_EnableUserSettings(hwnd, !channel->useDefaultUpdate);
}
ChannelEditor_Reposition(hwnd);
return TRUE;
}
static void ChannelEditor_OnDestroy(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
RemoveProp(hwnd, MAKEINTATOM(VIEWPROP));
}
static void ChannelEditor_UpdateUserSettings(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return;
HWND hControl;
FALSE;
hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
BOOL enableUser = (NULL != hControl && BST_CHECKED == (INT)SNDMSG(hControl, BM_GETCHECK, 0, 0L));
if (FALSE == enableUser)
{
ChannelEditor_SetUpdateTime(hwnd, updateTime, autoUpdate);
ChannelEditor_SetAutoDownload(hwnd, autoDownloadEpisodes, autoDownload);
}
else
{
ChannelEditor_SetUpdateTime(hwnd, editor->updateTime, editor->enableAutoUpdate);
ChannelEditor_SetAutoDownload(hwnd, editor->numOfAutoDownloadEpisodes, editor->enableAutoDownload);
}
const INT szControl[] = { IDC_UPDATELIST, IDC_AUTODOWNLOADLIST, IDC_STATIC_UPDATEEVERY, IDC_STATIC_AUTODOWNLOAD, };
for (INT i = 0; i < ARRAYSIZE(szControl); i++)
{
hControl = GetDlgItem(hwnd, szControl[i]);
if (NULL != hControl) EnableWindow(hControl, enableUser);
}
ChannelEditor_UpdateModifiedState(hwnd);
}
static INT_PTR ChannelEditor_SaveChannel(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return 0;
AutoLock lock (channels);
if (editor->iChannel >= channels.size())
return 0;
HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
if (NULL == hControl) return 0;
Channel *channel = &channels[editor->iChannel];
if (BST_CHECKED == SNDMSG(hControl, BM_GETCHECK, 0, 0L))
{
channel->useDefaultUpdate = false;
channel->autoDownload = (FALSE != editor->enableAutoDownload);
channel->updateTime = editor->updateTime;
channel->autoUpdate = (FALSE != editor->enableAutoUpdate);
channel->autoDownloadEpisodes = editor->numOfAutoDownloadEpisodes;
}
else
{
channel->useDefaultUpdate = true;
channel->autoDownload = autoDownload;
channel->updateTime = updateTime;
channel->autoUpdate = autoUpdate;
channel->autoDownloadEpisodes = autoDownloadEpisodes;
}
if (channel->autoUpdate)
cloud.Pulse();
return
IDOK;
}
static DWORD AddUrlThread(void *vBuffer, HWND hwndDlg)
{
Channel newFeed;
newFeed.autoUpdate = false; // TODO check defaults
newFeed.autoDownload = false; // leave this as false
newFeed.SetURL((const wchar_t *)vBuffer);
delete vBuffer;
int downloadError = DownloadFeedInformation(newFeed);
if (downloadError != DOWNLOAD_SUCCESS)
return downloadError;
// TODO check defaults;
if (IsDlgButtonChecked(hwndDlg, IDC_USECUSTOM) == BST_CHECKED)
{
LRESULT timeSelection;
timeSelection = SendMessage(GetDlgItem(hwndDlg, IDC_UPDATELIST), CB_GETCURSEL, 0, 0);
if (timeSelection != CB_ERR)
{
newFeed.autoUpdate = Update::GetAutoUpdate(timeSelection);
newFeed.updateTime = Update::GetTime(timeSelection);
}
LRESULT episodesSelection;
episodesSelection = SendMessage(GetDlgItem(hwndDlg, IDC_AUTODOWNLOADLIST), CB_GETCURSEL, 0, 0);
if (episodesSelection != CB_ERR)
{
newFeed.autoDownload = UpdateAutoDownload::GetAutoDownload(episodesSelection);
newFeed.autoDownloadEpisodes = UpdateAutoDownload::GetAutoDownloadEpisodes(episodesSelection);
}
newFeed.useDefaultUpdate = false;
}
else
{
newFeed.useDefaultUpdate = true;
newFeed.autoUpdate = autoUpdate;
newFeed.updateTime = updateTime;
newFeed.autoDownloadEpisodes = autoDownloadEpisodes;
newFeed.autoDownload = autoDownload;
}
//newFeed.title += L" ";
AutoLock lock (channels LOCKNAME("AddURL"));
if (!channels.AddChannel(newFeed))
{
wchar_t error_msg[1024] = {0}, titleStr[64] = {0};
StringCchPrintf( error_msg, 1024, WASABI_API_LNGSTRINGW( IDS_CHANNEL_ALREADY_PRESENT ), newFeed.title, newFeed.url );
MessageBox( hwndDlg, error_msg, WASABI_API_LNGSTRINGW_BUF( IDS_DUPLICATE_CHANNEL, titleStr, 64 ), MB_OK );
return DOWNLOAD_DUPLICATE;
}
else
{
cloud.Pulse(); // TODO why?
HWND hView = SubscriptionView_FindWindow();
if (NULL != hView)
{
SubscriptionView_RefreshChannels(hView, TRUE);
}
}
return DOWNLOAD_SUCCESS; // success
}
static INT ChannelEditor_AddUrl(HWND hwnd)
{
size_t length = GetWindowTextLength(GetDlgItem(hwnd, IDC_EDITURL)) + 1;
wchar_t *buffer = new wchar_t[length];
ZeroMemory(buffer,sizeof(wchar_t)*length);
GetDlgItemText(hwnd, IDC_EDITURL, buffer, (int)length);
return AddUrlThread((void *)buffer, hwnd);
}
static INT_PTR ChannelEditor_CreateChannel(HWND hwnd)
{
INT result = ChannelEditor_AddUrl(hwnd);
if (DOWNLOAD_SUCCESS == result)
return IDOK;
INT messageId = IDS_ERROR_ADDING_URL;
INT titleId = IDS_ERROR_ADDING_URL;
switch (result)
{
case DOWNLOAD_ERROR_PARSING_XML: messageId = IDS_ERROR_PARSING_XML_FROM_SERVER; break;
case DOWNLOAD_NOTRSS: messageId = IDS_LINK_HAS_NO_RSS_INFO; break;
case DOWNLOAD_404: messageId = IDS_INVALID_LINK; break;
case DOWNLOAD_NOHTTP: messageId = IDS_NO_JNETLIB; titleId = IDS_JNETLIB_MISSING; break;
case DOWNLOAD_NOPARSER: messageId = IDS_NO_EXPAT; titleId = IDS_EXPAT_MISSING; break;
case DOWNLOAD_CONNECTIONRESET: messageId = IDS_CONNECTION_RESET_BY_PEER; break;
}
if(result != DOWNLOAD_DUPLICATE)
{
WCHAR szMessage[512] = {0}, szTitle[128] = {0};
WASABI_API_LNGSTRINGW_BUF(messageId, szMessage, ARRAYSIZE(szMessage));
WASABI_API_LNGSTRINGW_BUF(titleId, szTitle, ARRAYSIZE(szTitle));
MessageBox(hwnd, szMessage, szTitle, MB_OK | MB_ICONERROR);
}
return IDIGNORE;
}
static void ChannelEditor_OnCancel(HWND hwnd)
{
EndDialog(hwnd, IDCANCEL);
}
static void ChannelEditor_OnOk(HWND hwnd)
{
INT_PTR result = 0;
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL != editor)
{
if (0 == (CEF_CREATENEW & editor->flags))
{
result = ChannelEditor_SaveChannel(hwnd);
}
else
{
result = ChannelEditor_CreateChannel(hwnd);
}
}
if (IDIGNORE != result)
{
EndDialog(hwnd, result);
}
}
#if 0
static void ChannelEditor_OnAutoDownloadChanged(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return;
HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
if (NULL == hControl || BST_CHECKED != SNDMSG(hControl, BM_GETCHECK, 0, 0L)) return;
hControl = GetDlgItem(hwnd, IDC_AUTODOWNLOAD);
if (NULL == hControl) return;
editor->enableAutoDownload = (BST_CHECKED == SNDMSG(hControl, BM_GETCHECK, 0, 0L));
ChannelEditor_UpdateModifiedState(hwnd);
}
#endif
static void ChannelEditor_OnUrlChange(HWND hwnd)
{
WCHAR szBuffer[4096] = {0};
HWND hControl = GetDlgItem(hwnd, IDC_EDITURL);
if (NULL == hControl || 0 == (INT)SNDMSG(hControl, WM_GETTEXT, (WPARAM)ARRAYSIZE(szBuffer), (LPARAM)szBuffer))
szBuffer[0] = L'\0';
LPCWSTR p = szBuffer;
while (L' ' == *p && L'\0' != *p) p++;
BOOL enableButton = (L'\0' != *p);
hControl = GetDlgItem(hwnd, IDOK);
if (NULL != hControl)
{
EnableWindow(hControl, enableButton);
}
}
static void ChannelEditor_OnUpdateTimeChange(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return;
HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
if (NULL == hControl || BST_CHECKED != SNDMSG(hControl, BM_GETCHECK, 0, 0L)) return;
hControl = GetDlgItem(hwnd, IDC_UPDATELIST);
if (NULL == hControl) return;
INT iSelected = (INT)SNDMSG(hControl, CB_GETCURSEL, 0, 0L);
if (CB_ERR == iSelected) return;
INT selectedVal = (INT)SNDMSG(hControl, CB_GETITEMDATA, iSelected, 0L);
if (selectedVal < 0 || selectedVal >= Update::TIME_NUMENTRIES) return;
editor->updateTime = Update::GetTime(selectedVal);
editor->enableAutoUpdate = Update::GetAutoUpdate(selectedVal);
ChannelEditor_UpdateModifiedState(hwnd);
}
static void ChannelEditor_OnUpdateAutoDownloadChange(HWND hwnd)
{
CHANNELEDITOR *editor = GetEditor(hwnd);
if (NULL == editor) return;
HWND hControl = GetDlgItem(hwnd, IDC_USECUSTOM);
if (NULL == hControl || BST_CHECKED != SNDMSG(hControl, BM_GETCHECK, 0, 0L)) return;
hControl = GetDlgItem(hwnd, IDC_AUTODOWNLOADLIST);
if (NULL == hControl) return;
INT iSelected = (INT)SNDMSG(hControl, CB_GETCURSEL, 0, 0L);
if (CB_ERR == iSelected) return;
INT selectedVal = (INT)SNDMSG(hControl, CB_GETITEMDATA, iSelected, 0L);
if (selectedVal < 0 || selectedVal >= UpdateAutoDownload::AUTODOWNLOAD_NUMENTRIES) return;
editor->numOfAutoDownloadEpisodes = UpdateAutoDownload::GetAutoDownloadEpisodes(selectedVal);
editor->enableAutoDownload = UpdateAutoDownload::GetAutoDownload(selectedVal);
ChannelEditor_UpdateModifiedState(hwnd);
}
static void ChannelEditor_OnCommand(HWND hwnd, UINT commandId, UINT eventId, HWND hControl)
{
switch(commandId)
{
case IDCANCEL: ChannelEditor_OnCancel(hwnd); break;
case IDOK: ChannelEditor_OnOk(hwnd); break;
case IDC_USEDEFAULTS:
case IDC_USECUSTOM:
switch(eventId)
{
case BN_CLICKED: ChannelEditor_UpdateUserSettings(hwnd); break;
}
break;
case IDC_EDITURL:
switch(eventId)
{
case EN_CHANGE: ChannelEditor_OnUrlChange(hwnd); break;
}
break;
case IDC_UPDATELIST:
switch(eventId)
{
case CBN_SELCHANGE: ChannelEditor_OnUpdateTimeChange(hwnd); break;
}
break;
case IDC_AUTODOWNLOADLIST:
switch(eventId)
{
case CBN_SELCHANGE: ChannelEditor_OnUpdateAutoDownloadChange(hwnd); break;
}
break;
}
}
static INT_PTR CALLBACK ChannelEditor_DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG: return ChannelEditor_OnInitDialog(hwnd, (HWND)wParam, lParam);
case WM_DESTROY: ChannelEditor_OnDestroy(hwnd); return TRUE;
case WM_COMMAND: ChannelEditor_OnCommand(hwnd, LOWORD(wParam), HIWORD(wParam), (HWND)lParam); return TRUE;
}
return 0;
}

View File

@ -0,0 +1,15 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_CHANNEL_EDITOR_HEADER
#define NULLSOFT_PODCAST_PLUGIN_CHANNEL_EDITOR_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
#define CEF_CREATENEW 0x00000001
#define CEF_CENTEROWNER 0x00000002
INT_PTR ChannelEditor_Show(HWND hOwner, size_t channelIndex, UINT flags);
#endif //NULLSOFT_PODCAST_PLUGIN_CHANNEL_EDITOR_HEADER

View File

@ -0,0 +1 @@
#include "time.h"

View File

@ -0,0 +1,20 @@
#ifndef _OD_DATE_
#define _OD_DATE_
#include <time.h>
/* These functions all work with "Internet"(RFC 822) format Date/Time strings only */
/* An example of an RFC 822 format Date/Time string is "Thu, 28 Aug 2003 21:30:47 EDT" */
/* converts the RFC 822 format date string into UTC Calendar time */
time_t getDateSecs(char *date);
/* returns a string that represents the given UTC Calendar time value as an
RFC 822 format string. The buffer is user-supplied and must be at least
30 bytes in size. */
char *getDateStr(time_t tmval, char *buffer, int gmt);
#endif /*_OD_DATE_*/

View File

@ -0,0 +1,171 @@
#include <shlwapi.h>
#include "api__ml_wire.h"
#include "Downloaded.h"
#include "../nde/nde_c.h"
#include "../nu/AutoLock.h"
/* DB Schema
ChannelTitle
ItemUrl
ItemTitle
PublishDate
Length
Filename
*/
static Nullsoft::Utility::LockGuard dbcs;
static nde_table_t table = 0;
static nde_database_t db = 0;
using namespace Nullsoft::Utility;
enum
{
DB_ID_CHANNELTITLE = 0,
DB_ID_ITEMURL = 1,
DB_ID_ITEMTITLE = 2,
DB_ID_PUBLISHDATE = 3,
DB_ID_LENGTH = 4,
DB_ID_FILENAME = 5
};
static bool OpenDatabase()
{
AutoLock lock(dbcs);
if (!db)
db = NDE_CreateDatabase();
return true;
}
void CloseDatabase()
{
AutoLock lock( dbcs );
if ( db )
{
if ( table )
NDE_Database_CloseTable( db, table );
NDE_DestroyDatabase( db );
}
db = 0;
}
static void CreateFields(nde_table_t table)
{
// create defaults
NDE_Table_NewColumnW(table, DB_ID_CHANNELTITLE, L"channeltitle", FIELD_STRING);
NDE_Table_NewColumnW(table, DB_ID_ITEMURL, L"itemurl", FIELD_STRING);
NDE_Table_NewColumnW(table, DB_ID_ITEMTITLE, L"itemtitle", FIELD_STRING);
NDE_Table_NewColumnW(table, DB_ID_PUBLISHDATE, L"publishdate", FIELD_DATETIME);
NDE_Table_NewColumnW(table, DB_ID_LENGTH, L"length", FIELD_INTEGER);
NDE_Table_NewColumnW(table, DB_ID_FILENAME, L"filename", FIELD_FILENAME);
NDE_Table_PostColumns(table);
NDE_Table_AddIndexByIDW(table, DB_ID_ITEMURL, L"itemurl");
}
static bool OpenTable()
{
AutoLock lock( dbcs );
if ( !OpenDatabase() )
return false;
if ( !table )
{
const wchar_t *inidir = WASABI_API_APP->path_getUserSettingsPath();
wchar_t tablePath[ MAX_PATH ] = { 0 }, indexPath[ MAX_PATH ] = { 0 };
PathCombineW( tablePath, inidir, L"plugins" );
PathAppendW( tablePath, L"podcasts.dat" );
PathCombineW( indexPath, inidir, L"plugins" );
PathAppendW( indexPath, L"podcasts.idx" );
table = NDE_Database_OpenTable( db, tablePath, indexPath, NDE_OPEN_ALWAYS, NDE_CACHE );
if ( table )
CreateFields( table );
}
return !!table;
}
static void db_add( nde_scanner_t s, unsigned char id, wchar_t *data )
{
if ( data )
{
nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
NDE_StringField_SetString( f, data );
}
}
static void db_add_int( nde_scanner_t s, unsigned char id, int data )
{
if ( data )
{
nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
NDE_IntegerField_SetValue( f, data );
}
}
static void db_add_time( nde_scanner_t s, unsigned char id, time_t data )
{
if ( data )
{
nde_field_t f = NDE_Scanner_NewFieldByID( s, id );
NDE_IntegerField_SetValue( f, static_cast<int>( data ) );
}
}
bool AddPodcastData( const DownloadedFile &data )
{
AutoLock lock( dbcs );
if ( !OpenTable() )
return false;
nde_scanner_t s = NDE_Table_CreateScanner( table );
if ( s )
{
NDE_Scanner_New( s );
db_add( s, DB_ID_CHANNELTITLE, data.channel );
db_add( s, DB_ID_ITEMURL, data.url );
db_add( s, DB_ID_ITEMTITLE, data.item );
db_add_time( s, DB_ID_PUBLISHDATE, data.publishDate );
db_add_int( s, DB_ID_LENGTH, (int)data.totalSize );
db_add( s, DB_ID_FILENAME, data.path );
NDE_Scanner_Post( s );
NDE_Table_DestroyScanner( table, s );
NDE_Table_Sync( table );
return true;
}
return false;
}
bool IsPodcastDownloaded( const wchar_t *url )
{
AutoLock lock( dbcs );
if ( !OpenTable() )
return false;
nde_scanner_t s = NDE_Table_CreateScanner( table );
if ( s )
{
if ( NDE_Scanner_LocateString( s, DB_ID_ITEMURL, FIRST_RECORD, url ) )
{
NDE_Table_DestroyScanner( table, s );
return true;
}
NDE_Table_DestroyScanner( table, s );
}
return false;
}
void CompactDatabase()
{
AutoLock lock( dbcs );
if ( OpenTable() )
NDE_Table_Compact( table );
}

View File

@ -0,0 +1,17 @@
#ifndef NULLSOFT_ML_WIRE_ERRORS_H
#define NULLSOFT_ML_WIRE_ERRORS_H
enum
{
DOWNLOAD_SUCCESS = 0,
DOWNLOAD_404,
DOWNLOAD_TIMEOUT,
DOWNLOAD_NOTRSS,
DOWNLOAD_DUPLICATE,
DOWNLOAD_NOHTTP,
DOWNLOAD_NOPARSER,
DOWNLOAD_CONNECTIONRESET,
DOWNLOAD_ERROR_PARSING_XML,
};
#endif

View File

@ -0,0 +1,15 @@
#ifndef NULLSOFT_ML_WIRE_IFC_ARTICLE_H
#define NULLSOFT_ML_WIRE_IFC_ARTICLE_H
#include <bfc/dispatch.h>
class ifc_article
{
protected:
ifc_article() {}
~ifc_article() {}
public:
};
#endif

View File

@ -0,0 +1,39 @@
#ifndef NULLSOFT_ML_WIRE_IFC_PODCAST_H
#define NULLSOFT_ML_WIRE_IFC_PODCAST_H
#include <bfc/dispatch.h>
class ifc_podcast : public Dispatchable
{
protected:
ifc_podcast() {}
~ifc_podcast() {}
public:
//int GetUrl(wchar_t *str, size_t len);
int GetTitle(wchar_t *str, size_t len);
//int GetLink(wchar_t *str, size_t len);
//int GetDescription(wchar_t *str, size_t len);
size_t GetNumArticles();
// TODO: ifc_article *EnumArticle(size_t i);
enum
{
IFC_PODCAST_GETURL = 0,
IFC_PODCAST_GETTITLE = 1,
IFC_PODCAST_GETLINK = 2,
IFC_PODCAST_GETDESCRIPTION = 3,
IFC_PODCAST_GETNUMARTICLES = 4,
};
};
inline int ifc_podcast::GetTitle(wchar_t *str, size_t len)
{
return _call(IFC_PODCAST_GETTITLE, (int)1, str, len);
}
inline size_t ifc_podcast::GetNumArticles()
{
return _call(IFC_PODCAST_GETNUMARTICLES, (size_t)0);
}
#endif

View File

@ -0,0 +1,215 @@
#include "./layout.h"
#include <windows.h>
BOOL Layout_Initialize( HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout )
{
if ( NULL == itemList || NULL == layout )
return FALSE;
LAYOUTITEM *item;
for ( INT i = 0; i < itemCount; i++ )
{
item = &layout[ i ];
item->hwnd = GetDlgItem( hwnd, itemList[ i ] );
if ( item->hwnd == NULL )
continue;
if ( FALSE == GetWindowRect( item->hwnd, &item->rect ) )
SetRectEmpty( &item->rect );
else
MapWindowPoints( HWND_DESKTOP, hwnd, (POINT *)&item->rect, 2 );
item->cx = item->rect.right - item->rect.left;
item->cy = item->rect.bottom - item->rect.top;
item->x = item->rect.left;
item->y = item->rect.top;
item->flags = 0;
}
return TRUE;
}
BOOL Layout_Perform( HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw )
{
HDWP hdwp, hdwpTemp;
hdwp = BeginDeferWindowPos( layoutCount );
if ( hdwp == NULL )
return FALSE;
UINT baseFlags = SWP_NOACTIVATE | SWP_NOZORDER;
if ( fRedraw == FALSE )
baseFlags |= ( SWP_NOREDRAW | SWP_NOCOPYBITS );
LAYOUTITEM *item;
for ( INT i = 0; i < layoutCount; i++ )
{
item = &layout[ i ];
if ( item->hwnd == NULL )
continue;
UINT flags = baseFlags | ( item->flags & ~( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) );
if ( item->x == item->rect.left && item->y == item->rect.top )
flags |= SWP_NOMOVE;
if ( item->cx == ( item->rect.right - item->rect.left ) && item->cy == ( item->rect.bottom - item->rect.top ) )
flags |= SWP_NOSIZE;
if ( ( SWP_HIDEWINDOW & item->flags ) != 0 )
{
UINT windowStyle = GetWindowLongPtr( item->hwnd, GWL_STYLE );
if ( ( WS_VISIBLE & windowStyle ) != 0 )
{
SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle & ~WS_VISIBLE );
if ( FALSE != fRedraw )
{
RedrawWindow( hwnd, &item->rect, NULL, RDW_INVALIDATE | RDW_ERASE );
}
}
}
if ( ( SWP_NOSIZE | SWP_NOMOVE ) != ( ( SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED ) & flags ) )
{
hdwpTemp = DeferWindowPos( hdwp, item->hwnd, NULL, item->x, item->y, item->cx, item->cy, flags );
if ( hdwpTemp == NULL )
break;
hdwp = hdwpTemp;
}
}
BOOL result = ( hdwp != NULL ) ? EndDeferWindowPos( hdwp ) : FALSE;
for ( INT i = 0; i < layoutCount; i++ )
{
item = &layout[ i ];
if ( NULL != item->hwnd && 0 != ( SWP_SHOWWINDOW & item->flags ) )
{
UINT windowStyle = GetWindowLongPtr( item->hwnd, GWL_STYLE );
if ( 0 == ( WS_VISIBLE & windowStyle ) )
{
SetWindowLongPtr( item->hwnd, GWL_STYLE, windowStyle | WS_VISIBLE );
if ( FALSE != fRedraw )
RedrawWindow( item->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN );
}
}
}
return result;
}
static void Layout_SetItemVisibility( const RECT *rect, LAYOUTITEM *item )
{
if ( NULL == item || NULL == item->hwnd )
return;
BOOL outsider = ( item->cx <= 0 || item->cy <= 0 ||
item->x >= rect->right || item->y >= rect->bottom ||
( item->x + item->cx ) < rect->left || ( item->y + item->cy ) < rect->top );
UINT windowStyle = GetWindowLongPtr( item->hwnd, GWL_STYLE );
if ( 0 == ( WS_VISIBLE & windowStyle ) )
{
if ( !outsider )
{
item->flags |= SWP_SHOWWINDOW;
}
}
else
{
if ( outsider )
{
item->flags |= SWP_HIDEWINDOW;
}
}
}
BOOL Layout_SetVisibility( const RECT *rect, LAYOUTITEM *layout, INT layoutCount )
{
if ( NULL == rect || NULL == layout )
return FALSE;
for ( INT i = 0; i < layoutCount; i++ )
{
Layout_SetItemVisibility( rect, &layout[ i ] );
}
return TRUE;
}
BOOL Layout_SetVisibilityEx( const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout )
{
if ( NULL == rect || NULL == indexList || NULL == layout )
return FALSE;
for ( INT i = 0; i < indexCount; i++ )
{
Layout_SetItemVisibility( rect, &layout[ indexList[ i ] ] );
}
return TRUE;
}
BOOL Layout_GetValidRgn( HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount )
{
if ( NULL == validRgn )
return FALSE;
SetRectRgn( validRgn, 0, 0, 0, 0 );
if ( NULL == layout )
return FALSE;
HRGN rgn = CreateRectRgn( 0, 0, 0, 0 );
if ( NULL == rgn )
return FALSE;
LAYOUTITEM *item;
LONG l, t, r, b;
for ( INT i = 0; i < layoutCount; i++ )
{
item = &layout[ i ];
if ( NULL != item->hwnd && 0 == ( ( SWP_HIDEWINDOW | SWP_SHOWWINDOW ) & item->flags ) )
{
l = item->x + parrentOffset.x;
t = item->y + parrentOffset.y;
r = l + item->cx;
b = t + item->cy;
if ( 0 != ( SWP_NOREDRAW & item->flags ) || ( l == item->rect.left && t == item->rect.top && r == item->rect.right && b == item->rect.bottom ) )
{
if ( NULL != validRect )
{
if ( l < validRect->left )
l = validRect->left;
if ( t < validRect->top )
t = validRect->top;
if ( r > validRect->right )
r = validRect->right;
if ( b > validRect->bottom )
b = validRect->bottom;
}
if ( l < r && t < b )
{
SetRectRgn( rgn, l, t, r, b );
CombineRgn( validRgn, validRgn, rgn, RGN_OR );
if ( NULLREGION != GetUpdateRgn( item->hwnd, rgn, FALSE ) )
{
OffsetRgn( rgn, parrentOffset.x, parrentOffset.y );
CombineRgn( validRgn, validRgn, rgn, RGN_DIFF );
}
}
}
}
}
DeleteObject( rgn );
return TRUE;
}

View File

@ -0,0 +1,43 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
#define NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
typedef struct __LAYOUTITEM
{
HWND hwnd;
LONG x;
LONG y;
LONG cx;
LONG cy;
UINT flags;
RECT rect;
} LAYOUTITEM;
#define LI_GET_R(__li) ((__li).x + (__li).cx)
#define LI_GET_B(__li) ((__li).y + (__li).cy)
#define LI_EXPAND_W(__li, __delta) { (__li).cx += (__delta); }
#define LI_EXPAND_H(__li, __delta) { (__li).cy += (__delta); }
#define LI_SHIFT_L(__li, __delta) { (__li).x += (__delta); }
#define LI_SHIFT_T(__li, __delta) { { (__li).y += (__delta); }
#define LI_SET_L(__li, __val) { (__li).x = (__val); }
#define LI_SET_T(__li, __val) { (__li).y = (__val); }
#define LI_SET_W(__li, __val) { (__li).cx = (__val); }
#define LI_SET_H(__li, __val) { (__li).cy = (__val); }
#define LI_SET_R(__li, __val) { (__li).cx = ((__val) - (__li).x); }
#define LI_SET_B(__li, __val) { (__li).cy = ((__val) - (__li).y); }
BOOL Layout_Initialize(HWND hwnd, const INT *itemList, INT itemCount, LAYOUTITEM *layout);
BOOL Layout_SetVisibilityEx(const RECT *rect, const INT *indexList, INT indexCount, LAYOUTITEM *layout);
BOOL Layout_SetVisibility(const RECT *rect, LAYOUTITEM *layout, INT layoutCount);
BOOL Layout_Perform(HWND hwnd, LAYOUTITEM *layout, INT layoutCount, BOOL fRedraw);
BOOL Layout_GetValidRgn(HRGN validRgn, POINTS parrentOffset, const RECT *validRect, LAYOUTITEM *layout, INT layoutCount);
#endif //NULLSOFT_PODCAST_PLUGIN_LAYOUT_HEADER

View File

@ -0,0 +1,396 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""afxres.h""\r\0"
END
3 TEXTINCLUDE
BEGIN
"#include ""version.rc2""\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_PODCAST DIALOGEX 0, 0, 285, 277
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
EXSTYLE WS_EX_CONTROLPARENT
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
CONTROL "",IDC_CHANNELLIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,0,122,181,WS_EX_CLIENTEDGE
CONTROL "",IDC_VDIV,"Static",SS_BLACKFRAME,122,0,5,182
CONTROL "",IDC_ITEMLIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,127,0,156,181,WS_EX_CLIENTEDGE
PUSHBUTTON "Directory",IDC_FINDNEW,2,183,41,11
PUSHBUTTON "Add",IDC_ADD,45,183,35,11
PUSHBUTTON "Edit",IDC_EDIT,83,183,35,11
PUSHBUTTON "Delete",IDC_DELETE,121,183,35,11
PUSHBUTTON "Update",IDC_REFRESH,159,183,35,11
CONTROL "",IDC_HDIV,"Static",SS_BLACKFRAME,0,194,285,5
CONTROL "",IDC_EPISODE_INFO,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,198,285,10,WS_EX_CLIENTEDGE
CONTROL "",IDC_DESCRIPTION,"Static",SS_BLACKRECT,0,208,283,55
PUSHBUTTON "Play",IDC_PLAY,0,266,37,11
PUSHBUTTON "Enqueue",IDC_ENQUEUE,39,266,37,11
PUSHBUTTON "Download",IDC_DOWNLOAD,78,266,40,11
PUSHBUTTON "Visit site",IDC_VISIT,120,266,40,11
CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_WORDELLIPSIS | WS_GROUP,164,266,120,11
END
IDD_ADDURL DIALOGEX 0, 0, 270, 90
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_CONTROLPARENT
CAPTION "Add RSS Subscription"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
EDITTEXT IDC_EDITURL,4,7,262,14,ES_AUTOHSCROLL
CONTROL "Use default settings",IDC_USEDEFAULTS,"Button",BS_AUTORADIOBUTTON,8,25,88,10
CONTROL "Use custom settings",IDC_USECUSTOM,"Button",BS_AUTORADIOBUTTON,114,25,93,10
LTEXT "Update Every:",IDC_STATIC_UPDATEEVERY,8,42,48,8,WS_DISABLED
COMBOBOX IDC_UPDATELIST,59,40,70,95,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP
LTEXT "Download New Episodes:",IDC_STATIC_AUTODOWNLOAD,8,58,81,8,WS_DISABLED
COMBOBOX IDC_AUTODOWNLOADLIST,93,56,122,30,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Add",IDOK,162,73,50,13
PUSHBUTTON "Cancel",IDCANCEL,216,73,50,13
END
IDD_DOWNLOADS DIALOGEX 0, 0, 266, 92
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
EXSTYLE WS_EX_CONTROLPARENT
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
CONTROL "",IDC_DOWNLOADLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_TABSTOP,0,0,264,79
CONTROL "Play",IDC_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP,0,81,37,11
CONTROL "Enqueue",IDC_ENQUEUE,"Button",BS_OWNERDRAW | WS_TABSTOP,40,81,37,11
CONTROL "Remove",IDC_REMOVE,"Button",BS_OWNERDRAW | WS_TABSTOP,80,81,37,11
CONTROL "Clean up",IDC_CLEANUP,"Button",BS_OWNERDRAW | WS_TABSTOP,120,81,37,11
CONTROL "",IDC_STATUS,"Static",SS_LEFTNOWORDWRAP | SS_CENTERIMAGE | SS_ENDELLIPSIS | WS_GROUP,163,81,102,11
END
IDD_PREFERENCES DIALOGEX 0, 0, 272, 247
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU
EXSTYLE WS_EX_CONTROLPARENT
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
GROUPBOX "Podcasts",IDC_STATIC,0,0,272,246
GROUPBOX "Subscription Updates",IDC_STATIC,5,11,260,53
LTEXT "Update every:",IDC_STATICUPDATEEVERY,11,28,50,9,SS_CENTERIMAGE
COMBOBOX IDC_UPDATELIST,65,27,94,131,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
CONTROL "Update on launch",IDC_UPDATEONLAUNCH,"Button",BS_AUTOCHECKBOX | BS_LEFT | WS_TABSTOP,181,28,72,10
LTEXT "Download New Episodes:",IDC_STATICAUTODOWNLOAD,11,47,84,9
COMBOBOX IDC_AUTODOWNLOADLIST,99,45,112,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
GROUPBOX "Podcast Download Location",IDC_STATIC,5,67,260,34
EDITTEXT IDC_DOWNLOADLOCATION,11,81,198,13,ES_AUTOHSCROLL
PUSHBUTTON "Browse...",IDC_BROWSE,209,81,50,13
GROUPBOX "'Podcast Directory' Service",IDC_STATIC,5,105,260,72
LTEXT "This allows you to specify an alternative Podcast Directory service to display in the 'Podcast Directory' node. Leave blank to reset to the default service.",IDC_STATIC,11,116,248,18
EDITTEXT IDC_DIRECTORYURL,11,138,248,13,ES_AUTOHSCROLL
LTEXT "Note: To subscribe to feeds when using an alternative Podcast Directory service, the service will need to use the available Winamp Javascript API.",IDC_STATIC,11,155,248,18,WS_DISABLED
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_ADDURL, DIALOG
BEGIN
LEFTMARGIN, 4
RIGHTMARGIN, 266
TOPMARGIN, 7
BOTTOMMARGIN, 86
END
IDD_PREFERENCES, DIALOG
BEGIN
BOTTOMMARGIN, 246
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDR_MENU1 MENU
BEGIN
POPUP "Podcast"
BEGIN
MENUITEM "Update podcast\tF5", IDC_REFRESH
MENUITEM "Edit podcast...\tF2", IDC_EDIT
MENUITEM "Delete podcast\tDel", IDC_DELETE
MENUITEM SEPARATOR
MENUITEM "Visit site\tF7", IDC_VISIT
MENUITEM SEPARATOR
MENUITEM "Add podcast...\tInsert", IDC_ADD
MENUITEM "Update all\tShift+F5", IDC_REFRESHALL
END
POPUP "Episode"
BEGIN
MENUITEM "Play media", IDC_PLAY
MENUITEM "Enqueue media", IDC_ENQUEUE
POPUP "Send to:"
BEGIN
MENUITEM "", ID_Menu
END
MENUITEM SEPARATOR
MENUITEM "Download media", IDC_DOWNLOAD
MENUITEM SEPARATOR
MENUITEM "Select all\tCtrl+A", IDC_SELECTALL
MENUITEM "Explore media folder\tCtrl+F", ID_DOWNLOADS_EXPLORERITEMFOLDER
MENUITEM "Visit site\tF7", IDC_VISIT
MENUITEM SEPARATOR
MENUITEM "Update podcast\tF5", IDC_REFRESH
END
POPUP "Downloads"
BEGIN
MENUITEM "Play file\tEnter", IDC_PLAY
MENUITEM "Enqueue file\tShift+Enter", IDC_ENQUEUE
POPUP "Send to:"
BEGIN
MENUITEM "", ID_Menu
END
MENUITEM SEPARATOR
MENUITEM "Select all\tCtrl+A", IDC_SELECTALL
MENUITEM "View file info...\tAlt+3", IDC_INFOBOX
MENUITEM "Explore item folder\tCtrl+F", ID_DOWNLOADS_EXPLORERITEMFOLDER
MENUITEM SEPARATOR
MENUITEM "Remove from list\tDel", IDC_REMOVE
MENUITEM "Delete file\tShift+Del", IDC_DELETE
END
POPUP "Navigation"
BEGIN
MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
MENUITEM SEPARATOR
MENUITEM "Help", ID_NAVIGATION_HELP
END
POPUP "SubscriptionNavigation"
BEGIN
MENUITEM "Directory", ID_NAVIGATION_DIRECTORY
MENUITEM "&Preferences", ID_NAVIGATION_PREFERENCES
MENUITEM SEPARATOR
MENUITEM "Update all", ID_NAVIGATION_REFRESHALL
MENUITEM SEPARATOR
MENUITEM "Help", ID_NAVIGATION_HELP
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Accelerator
//
IDR_VIEW_DOWNLOAD_ACCELERATORS ACCELERATORS
BEGIN
"F", ID_DOWNLOADS_EXPLORERITEMFOLDER, VIRTKEY, CONTROL, NOINVERT
VK_RETURN, IDC_CUSTOM, VIRTKEY, SHIFT, CONTROL, NOINVERT
VK_DELETE, IDC_DELETE, VIRTKEY, SHIFT, NOINVERT
VK_RETURN, IDC_ENQUEUE, VIRTKEY, SHIFT, NOINVERT
"3", IDC_INFOBOX, VIRTKEY, ALT, NOINVERT
VK_RETURN, IDC_PLAY, VIRTKEY, NOINVERT
VK_DELETE, IDC_REMOVE, VIRTKEY, NOINVERT
"A", IDC_SELECTALL, VIRTKEY, CONTROL, NOINVERT
VK_F7, IDC_VISIT, VIRTKEY, NOINVERT
VK_F5, IDC_REFRESH, VIRTKEY, NOINVERT
"D", IDC_DOWNLOAD, VIRTKEY, CONTROL, NOINVERT
END
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_PLUGIN_NAME "Nullsoft Podcasts v%d.%02d"
65535 "{1FF327B2-A41D-4c67-A58A-EB09BA1470D3}"
END
STRINGTABLE
BEGIN
IDS_PODCASTS "Podcasts"
IDS_DOWNLOADING_KB_COMPLETE "Downloading %u files, %ukb complete."
IDS_DOWNLOADING_KB_PROGRESS
"Downloading %u files, %ukb of %ukb complete (%d%%)."
IDS_SUBSCRIPTIONS "Subscriptions"
IDS_DOWNLOADS "Downloads"
IDS_DOWNLOADING "Downloading..."
IDS_CHOOSE_FOLDER "Choose folder to store downloaded media."
IDS_ALREADY_SUBSCRIBED "You are already subscribed to %s.\n%s"
IDS_FILE_NOT_FOUND "Cannot connect to %s\nFile not Found."
IDS_CONNECTION_TIMED_OUT "Cannot connect to %s\nConnection timed out."
IDS_ERROR_PARSING_XML "Subscription to %s failed.\nError parsing XML data from server."
IDS_INVALID_RSS_FEED "Subscription to %s failed.\nDoes not appear to be a valid RSS feed."
IDS_NO_JNETLIB "HTTP Downloader library is missing.\nPlease reinstall Winamp."
IDS_JNETLIB_MISSING "JNetLib missing"
IDS_NO_EXPAT "XML Parsing library is missing.\nPlease reinstall Winamp."
END
STRINGTABLE
BEGIN
IDS_EXPAT_MISSING "Expat missing"
IDS_CONNECTION_RESET "Subscription to %s failed.\nConnection reset by peer."
IDS_ERROR_SUBSCRIBING_TO_PODCAST "Error Subscribing to Podcast"
IDS_UPD_MANUALLY "Manually"
IDS_UPD_WEEK "Week"
IDS_UPD_DAY "Day"
IDS_UPD_12HRS "12 hours"
IDS_UPD_6HRS "6 hours"
IDS_UPD_3HRS "3 hours"
IDS_UPD_HOUR "Hour"
IDS_UPD_30MINS "30 minutes"
IDS_ERROR_FYEO "ERROR ! ! YOU SHOULDN'T SEE THIS"
IDS_WDAY_SUN "Sun"
IDS_WDAY_MON "Mon"
IDS_WDAY_TUE "Tue"
IDS_WDAY_WED "Wed"
END
STRINGTABLE
BEGIN
IDS_WDAY_THU "Thu"
IDS_WDAY_FRI "Fri"
IDS_WDAY_SAT "Sat"
IDS_MONTH_JAN "Jan"
IDS_MONTH_FEB "Feb"
IDS_MONTH_MAR "Mar"
IDS_MONTH_APR "Apr"
IDS_MONTH_MAY "May"
IDS_MONTH_JUN "Jun"
IDS_MONTH_JUL "Jul"
IDS_MONTH_AUG "Aug"
IDS_MONTH_SEP "Sep"
IDS_MONTH_OCT "Oct"
IDS_MONTH_NOV "Nov"
IDS_MONTH_DEC "Dec"
IDS_RECEIVING_UPDATES_FOR "Retrieving updates for "
END
STRINGTABLE
BEGIN
IDS_GOT_NEW_ITEMS_FOR "Got new items for "
IDS_CHANNEL_ALREADY_PRESENT "Podcast Already Present:\n%s\n%s"
IDS_DUPLICATE_CHANNEL "Duplicate Podcast"
IDS_ERROR_ADDING_URL "Error adding URL"
IDS_ERROR_PARSING_XML_FROM_SERVER "Error parsing XML data from server"
IDS_LINK_HAS_NO_RSS_INFO "Link does not contain valid RSS information"
IDS_INVALID_LINK "Invalid link (404 or timeout)"
IDS_CONNECTION_RESET_BY_PEER "Connection reset by peer."
IDS_DOWNLOADING_PERCENT "Downloading %d%%"
IDS_CHANNEL "Podcast"
IDS_ITEM "Episode"
IDS_PROGRESS "Progress"
IDS_PATH "Path"
IDS_PERM_DELETE_ARE_YOU_SURE
"This will permanently delete this file, are you sure?"
IDS_PERM_DELETE_THESE_ARE_YOU_SURE
"This will permanently delete these %d files, are you sure?"
IDS_DELETION "Deletion"
END
STRINGTABLE
BEGIN
IDS_CLEAR_ALL_FINISHED_DOWNLOADS
"This will clear all finished downloads, are you sure?\n\nTip: You can view all your downloaded podcasts in Local Media/Podcasts"
IDS_CLEAN_UP_LIST "Clean Up List"
IDS_DONE "Done"
IDS_SAVE "Save"
IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION
"The media library feature you are attempting to use requires an internet connection. Please make sure you are connected to the internet and try again."
IDS_ADD_TO_DOWNLOADS "Added to downloads."
IDS_NO_MEDIA_TO_DOWNLOAD "No media to download."
IDS_TEXT_ARTICLE "Text article"
IDS_DATE_ADDED "Date Added"
IDS_MEDIA_PRESENT "Media Present"
IDS_SURE_YOU_WANT_TO_REMOVE_THIS
"Are you sure you want to remove %s\n(%s)"
IDS_CONFIRM "Confirm"
IDS_CANCEL_DOWNLOADS_AND_QUIT
"You are currently downloading podcasts\nAre you sure you want to cancel these downloads and quit?"
IDS_CONFIRM_QUIT "Confirm Quit"
IDS_ARTICLE_WITH_MEDIA "Article with media"
IDS_PODCAST_DIRECTORY "Podcast Directory"
END
STRINGTABLE
BEGIN
IDS_DELETEFAILED "Delete Failed"
IDS_SORRY "Sorry"
IDS_EDIT_CHANNEL "Edit Podcast"
IDS_ATD_NEVER "Never"
IDS_ATD_LASTONE "Most recent episode"
IDS_ATD_LASTTWO "Last 2 episodes"
IDS_ATD_LASTTHREE "Last 3 episodes"
IDS_ATD_LASTFIVE "Last 5 episodes"
END
STRINGTABLE
BEGIN
IDS_PODCAST_SUBSCRIPTION_HEADER "Winamp Podcast Subscription"
IDS_PODCAST_SUBSCRIPTION_PROMP
"Are you sure that you want to subscribe to this podcast?\n\n%s"
IDS_MEDIA_TIME "Time"
IDS_EPISODE_INFO "Episode Info"
IDS_MEDIA_SIZE "Size"
END
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
#include "version.rc2"
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,87 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29509.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_wire", "ml_wire.vcxproj", "{2BF2E8A5-18E0-47B4-822C-FF17077926FD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
ProjectSection(ProjectDependencies) = postProject
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
ProjectSection(ProjectDependencies) = postProject
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11} = {44AEBB50-1331-4F2E-8AEC-56C82DE16C11}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Debug|x64 = Debug|x64
Release|Win32 = Release|Win32
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|Win32.ActiveCfg = Debug|Win32
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|Win32.Build.0 = Debug|Win32
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|x64.ActiveCfg = Debug|x64
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Debug|x64.Build.0 = Debug|x64
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|Win32.ActiveCfg = Release|Win32
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|Win32.Build.0 = Release|Win32
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|x64.ActiveCfg = Release|x64
{2BF2E8A5-18E0-47B4-822C-FF17077926FD}.Release|x64.Build.0 = Release|x64
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
{4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
{57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
{E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
{E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
{E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
{E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
{E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
{E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
{E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
{E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.ActiveCfg = Debug|Win32
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|Win32.Build.0 = Debug|Win32
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.ActiveCfg = Debug|x64
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Debug|x64.Build.0 = Debug|x64
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.ActiveCfg = Release|Win32
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|Win32.Build.0 = Release|Win32
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.ActiveCfg = Release|x64
{44AEBB50-1331-4F2E-8AEC-56C82DE16C11}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,423 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{2BF2E8A5-18E0-47B4-822C-FF17077926FD}</ProjectGuid>
<RootNamespace>ml_wire</RootNamespace>
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
<IncludePath>$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
<IncludePath>$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
<EmbedManifest>true</EmbedManifest>
</PropertyGroup>
<PropertyGroup Label="Vcpkg">
<VcpkgEnableManifest>false</VcpkgEnableManifest>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<VcpkgInstalledDir>
</VcpkgInstalledDir>
<VcpkgUseStatic>false</VcpkgUseStatic>
<VcpkgConfiguration>Debug</VcpkgConfiguration>
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<VcpkgInstalledDir>
</VcpkgInstalledDir>
<VcpkgUseStatic>false</VcpkgUseStatic>
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<VcpkgInstalledDir>
</VcpkgInstalledDir>
<VcpkgUseStatic>false</VcpkgUseStatic>
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
<VcpkgConfiguration>Debug</VcpkgConfiguration>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<VcpkgInstalledDir>
</VcpkgInstalledDir>
<VcpkgUseStatic>false</VcpkgUseStatic>
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_DEBUG;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<MapFileName>$(IntDir)$(TargetName).map</MapFileName>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
<ResourceCompile>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Manifest>
<OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
</Manifest>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;_DEBUG;WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<SubSystem>Windows</SubSystem>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<MapFileName>$(IntDir)$(TargetName).map</MapFileName>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
<ResourceCompile>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Manifest>
<OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
</Manifest>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;IGNORE_API_GRACENOTE;NDEBUG;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>None</DebugInformationFormat>
<DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>false</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<MapFileName>$(IntDir)$(TargetName).map</MapFileName>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
<ResourceCompile>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Manifest>
<OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
</Manifest>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<AdditionalIncludeDirectories>.;..\..\..\Wasabi;..\..\..\replicant;..;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_WIN32_IE=0x0A00;IGNORE_API_GRACENOTE;NDEBUG;WIN64;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<StringPooling>true</StringPooling>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<BufferSecurityCheck>true</BufferSecurityCheck>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>None</DebugInformationFormat>
<DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
</ClCompile>
<Link>
<AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<GenerateDebugInformation>false</GenerateDebugInformation>
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
<MapFileName>$(IntDir)$(TargetName).map</MapFileName>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<RandomizedBaseAddress>false</RandomizedBaseAddress>
<ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
</Link>
<PostBuildEvent>
<Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command>
<Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
</PostBuildEvent>
<ResourceCompile>
<PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Manifest>
<OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
</Manifest>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\nde\nde.vcxproj">
<Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
<CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies>
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
<Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\WAT\WAT.vcxproj">
<Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\General\gen_ml\menu.h" />
<ClInclude Include="..\..\..\nu\Alias.h" />
<ClInclude Include="..\..\..\nu\ChildSizer.h" />
<ClInclude Include="..\..\..\nu\DialogSkinner.h" />
<ClInclude Include="..\..\..\nu\MediaLibraryInterface.h" />
<ClInclude Include="..\..\..\nu\menushortcuts.h" />
<ClInclude Include="..\..\..\xml\XMLDOM.h" />
<ClInclude Include="..\..\..\xml\XMLNode.h" />
<ClInclude Include="api__ml_wire.h" />
<ClInclude Include="api_podcasts.h" />
<ClInclude Include="BackgroundDownloader.h" />
<ClInclude Include="ChannelCheck.h" />
<ClInclude Include="channelEditor.h" />
<ClInclude Include="ChannelRefresher.h" />
<ClInclude Include="ChannelSync.h" />
<ClInclude Include="Cloud.h" />
<ClInclude Include="Defaults.h" />
<ClInclude Include="Downloaded.h" />
<ClInclude Include="DownloadsDialog.h" />
<ClInclude Include="DownloadsParse.h" />
<ClInclude Include="DownloadStatus.h" />
<ClInclude Include="DownloadThread.h" />
<ClInclude Include="errors.h" />
<ClInclude Include="ExternalCOM.h" />
<ClInclude Include="Factory.h" />
<ClInclude Include="FeedParse.h" />
<ClInclude Include="Feeds.h" />
<ClInclude Include="FeedUtil.h" />
<ClInclude Include="ifc_article.h" />
<ClInclude Include="ifc_podcast.h" />
<ClInclude Include="Item.h" />
<ClInclude Include="JSAPI2_Creator.h" />
<ClInclude Include="JSAPI2_PodcastsAPI.h" />
<ClInclude Include="layout.h" />
<ClInclude Include="Main.h" />
<ClInclude Include="navigation.h" />
<ClInclude Include="OPMLParse.h" />
<ClInclude Include="ParseUtil.h" />
<ClInclude Include="PCastFactory.h" />
<ClInclude Include="PCastURIHandler.h" />
<ClInclude Include="Preferences.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="RFCDate.h" />
<ClInclude Include="RSSCOM.h" />
<ClInclude Include="RSSParse.h" />
<ClInclude Include="service.h" />
<ClInclude Include="subscriptionView.h" />
<ClInclude Include="UpdateAutoDownload.h" />
<ClInclude Include="UpdateTime.h" />
<ClInclude Include="Util.h" />
<ClInclude Include="WantsDownloadStatus.h" />
<ClInclude Include="Wire.h" />
<ClInclude Include="XMLWriter.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\General\gen_ml\menu.cpp" />
<ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" />
<ClCompile Include="..\..\..\nu\ChildSizer.cpp" />
<ClCompile Include="..\..\..\nu\DialogSkinner.cpp" />
<ClCompile Include="..\..\..\nu\listview.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)1.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp" />
<ClCompile Include="..\..\..\nu\menushortcuts.cpp" />
<ClCompile Include="..\..\..\xml\XMLDOM.cpp" />
<ClCompile Include="..\..\..\xml\XMLNode.cpp" />
<ClCompile Include="BackgroundDownloader.cpp" />
<ClCompile Include="channelEditor.cpp" />
<ClCompile Include="ChannelRefresher.cpp" />
<ClCompile Include="Cloud.cpp" />
<ClCompile Include="db.cpp" />
<ClCompile Include="Defaults.cpp" />
<ClCompile Include="Downloaded.cpp" />
<ClCompile Include="DownloadsDialog.cpp" />
<ClCompile Include="DownloadsParse.cpp" />
<ClCompile Include="DownloadStatus.cpp" />
<ClCompile Include="DownloadThread.cpp" />
<ClCompile Include="ExternalCOM.cpp" />
<ClCompile Include="Factory.cpp" />
<ClCompile Include="FeedParse.cpp" />
<ClCompile Include="Feeds.cpp" />
<ClCompile Include="FeedUtil.cpp" />
<ClCompile Include="item.cpp" />
<ClCompile Include="JSAPI2_Creator.cpp" />
<ClCompile Include="JSAPI2_PodcastsAPI.cpp" />
<ClCompile Include="layout.cpp" />
<ClCompile Include="Main.cpp" />
<ClCompile Include="navigation.cpp" />
<ClCompile Include="OPMLParse.cpp" />
<ClCompile Include="ParseUtil.cpp" />
<ClCompile Include="PCastFactory.cpp" />
<ClCompile Include="PCastURIHandler.cpp" />
<ClCompile Include="Preferences.cpp" />
<ClCompile Include="RFCDate.cpp" />
<ClCompile Include="RSSCOM.cpp" />
<ClCompile Include="RSSParse.cpp" />
<ClCompile Include="service.cpp" />
<ClCompile Include="subscriptionView.cpp" />
<ClCompile Include="UpdateAutoDownload.cpp" />
<ClCompile Include="UpdateTime.cpp" />
<ClCompile Include="util.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)%(Filename)2.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="Wire.cpp" />
<ClCompile Include="XMLWriter.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ml_podcast.rc" />
<ResourceCompile Include="png.rc" />
</ItemGroup>
<ItemGroup>
<Text Include="DESIGN.txt" />
<Text Include="TODO.txt" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,345 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="BackgroundDownloader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="channelEditor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ChannelRefresher.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Cloud.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="db.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Defaults.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Downloaded.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DownloadsDialog.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DownloadsParse.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DownloadStatus.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DownloadThread.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ExternalCOM.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Factory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FeedParse.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Feeds.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FeedUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="JSAPI2_Creator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="JSAPI2_PodcastsAPI.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="layout.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="navigation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="OPMLParse.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ParseUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PCastFactory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PCastURIHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="XMLWriter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Preferences.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RFCDate.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RSSCOM.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RSSParse.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="service.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="subscriptionView.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UpdateAutoDownload.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UpdateTime.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Wire.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\ChildSizer.cpp">
<Filter>Source Files\nu</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\DialogSkinner.cpp">
<Filter>Source Files\nu</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\listview.cpp">
<Filter>Source Files\nu</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\MediaLibraryInterface.cpp">
<Filter>Source Files\nu</Filter>
</ClCompile>
<ClCompile Include="..\..\General\gen_ml\menu.cpp">
<Filter>Source Files\gen_ml</Filter>
</ClCompile>
<ClCompile Include="..\..\..\nu\menushortcuts.cpp">
<Filter>Source Files\nu</Filter>
</ClCompile>
<ClCompile Include="..\..\General\gen_ml\ml_lib.cpp">
<Filter>Source Files\gen_ml</Filter>
</ClCompile>
<ClCompile Include="..\..\..\xml\XMLNode.cpp">
<Filter>Source Files\XML</Filter>
</ClCompile>
<ClCompile Include="..\..\..\xml\XMLDOM.cpp">
<Filter>Source Files\XML</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="api__ml_wire.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="api_podcasts.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BackgroundDownloader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ChannelCheck.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="channelEditor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ChannelRefresher.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ChannelSync.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Cloud.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Defaults.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Downloaded.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DownloadsDialog.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DownloadsParse.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DownloadStatus.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DownloadThread.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="errors.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ExternalCOM.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Factory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FeedParse.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Feeds.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FeedUtil.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ifc_article.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ifc_podcast.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Item.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="JSAPI2_Creator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="JSAPI2_PodcastsAPI.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="layout.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Main.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="navigation.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="OPMLParse.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ParseUtil.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PCastFactory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PCastURIHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Preferences.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RFCDate.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RSSCOM.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RSSParse.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="service.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="subscriptionView.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="UpdateAutoDownload.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="UpdateTime.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Util.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WantsDownloadStatus.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Wire.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="XMLWriter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\Alias.h">
<Filter>Header Files\nu</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\ChildSizer.h">
<Filter>Header Files\nu</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\DialogSkinner.h">
<Filter>Header Files\nu</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\MediaLibraryInterface.h">
<Filter>Header Files\nu</Filter>
</ClInclude>
<ClInclude Include="..\..\General\gen_ml\menu.h">
<Filter>Header Files\gen_ml</Filter>
</ClInclude>
<ClInclude Include="..\..\..\nu\menushortcuts.h">
<Filter>Header Files\nu</Filter>
</ClInclude>
<ClInclude Include="..\..\..\xml\XMLNode.h">
<Filter>Header Files\xml</Filter>
</ClInclude>
<ClInclude Include="..\..\..\xml\XMLDOM.h">
<Filter>Header Files\xml</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ml_podcast.rc">
<Filter>Ressource Files</Filter>
</ResourceCompile>
<ResourceCompile Include="png.rc">
<Filter>Ressource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Text Include="DESIGN.txt" />
<Text Include="TODO.txt" />
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{04424438-0f00-45d3-957e-f60ad4e3addc}</UniqueIdentifier>
</Filter>
<Filter Include="Ressource Files">
<UniqueIdentifier>{ab23d144-c22c-4693-98c5-bc8255a28f57}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{8ca72875-5c2c-4336-984e-9aa6e1b15b2f}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\nu">
<UniqueIdentifier>{d8fe42ef-d543-46b2-9045-646ded85494d}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\gen_ml">
<UniqueIdentifier>{e6bd8d09-5297-423e-943c-22b3f0982d40}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\XML">
<UniqueIdentifier>{59f272b6-8c65-4db5-95b5-bca0712a07a8}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\nu">
<UniqueIdentifier>{d47a4f2b-363d-4281-aa4f-d76fdb3a22f6}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\gen_ml">
<UniqueIdentifier>{df263acf-3409-47f8-9bee-6090095c081c}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\xml">
<UniqueIdentifier>{b38287f4-c45a-41f8-9787-fe36ed08cb08}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@ -0,0 +1,420 @@
#include <strsafe.h>
#include "main.h"
#include "./navigation.h"
#include "./util.h"
#include "./resource.h"
#include "api__ml_wire.h"
#include "./service.h"
#include "./subscriptionView.h"
#include "./downloadsDialog.h"
#include "../omBrowser/browserView.h"
#include "../winamp/wa_ipc.h"
#include "..\..\General\gen_ml/ml_ipc_0313.h"
#include "./Defaults.h"
#define NAVITEM_PREFIX L"podcast_svc_"
#define E_NAVITEM_UNKNOWN E_NOINTERFACE
static Nullsoft::Utility::LockGuard navigationLock;
static INT Navigation_RegisterIcon( HWND hLibrary, INT iconIndex, LPCWSTR pszImage )
{
HMLIMGLST hmlilNavigation = MLNavCtrl_GetImageList( hLibrary );
if ( hmlilNavigation == NULL )
return -1;
MLIMAGESOURCE mlis;
ZeroMemory( &mlis, sizeof( MLIMAGESOURCE ) );
mlis.cbSize = sizeof( MLIMAGESOURCE );
mlis.hInst = NULL;
mlis.bpp = 24;
mlis.lpszName = pszImage;
mlis.type = SRC_TYPE_PNG;
mlis.flags = ISF_FORCE_BPP | ISF_PREMULTIPLY | ISF_LOADFROMFILE;
MLIMAGELISTITEM item;
ZeroMemory( &item, sizeof( MLIMAGELISTITEM ) );
item.cbSize = sizeof( MLIMAGELISTITEM );
item.hmlil = hmlilNavigation;
item.filterUID = MLIF_FILTER3_UID;
item.pmlImgSource = &mlis;
if (iconIndex >= 0)
{
INT count = MLImageList_GetImageCount( hLibrary, item.hmlil );
if (iconIndex < count)
{
item.mlilIndex = iconIndex;
return (FALSE != MLImageList_Replace(hLibrary, &item)) ? iconIndex : -1;
}
}
return MLImageList_Add( hLibrary, &item );
}
static HNAVITEM Navigation_CreateItem( HWND hLibrary, HNAVITEM hParent, OmService *service )
{
if ( hLibrary == NULL || service == NULL )
return NULL;
WCHAR szName[ 256 ] = { 0 }, szInvariant[ 64 ] = { 0 };
if ( FAILED( service->GetName( szName, ARRAYSIZE( szName ) ) ) )
return NULL;
if ( FAILED( StringCchPrintf( szInvariant, ARRAYSIZE( szInvariant ), NAVITEM_PREFIX L"%u", service->GetId() ) ) )
return NULL;
NAVINSERTSTRUCT nis = { 0 };
nis.hInsertAfter = NULL;
nis.hParent = hParent;
WCHAR szIcon[ 512 ] = { 0 };
INT iIcon = ( SUCCEEDED( service->GetIcon( szIcon, ARRAYSIZE( szIcon ) ) ) ) ? Navigation_RegisterIcon( hLibrary, -1, szIcon ) : -1;
nis.item.cbSize = sizeof( NAVITEM );
nis.item.mask = NIMF_TEXT | NIMF_STYLE | NIMF_TEXTINVARIANT | NIMF_PARAM | NIMF_IMAGE | NIMF_IMAGESEL;
nis.item.id = 0;
nis.item.pszText = szName;
nis.item.pszInvariant = szInvariant;
nis.item.lParam = (LPARAM)service;
nis.item.style = 0;
UINT serviceFlags = service->GetFlags();
if ( ( OmService::flagRoot & serviceFlags ) != 0 )
nis.item.style |= ( NIS_HASCHILDREN | NIS_DEFAULTIMAGE );
nis.item.styleMask = nis.item.style;
nis.item.iImage = iIcon;
nis.item.iSelectedImage = iIcon;
HNAVITEM hItem = MLNavCtrl_InsertItem( hLibrary, &nis );
if ( hItem != NULL )
service->AddRef();
return hItem;
}
static HNAVITEM Navigation_GetMessageItem( INT msg, INT_PTR param1 )
{
HWND hLibrary = plugin.hwndLibraryParent;
HNAVITEM hItem = ( msg < ML_MSG_NAVIGATION_FIRST ) ? MLNavCtrl_FindItemById( hLibrary, param1 ) : (HNAVITEM)param1;
return hItem;
}
static BOOL Navigation_CheckInvariantName( LPCWSTR pszInvarian )
{
INT cchInvariant = ( NULL != pszInvarian ) ? lstrlen( pszInvarian ) : 0;
INT cchPrefix = ARRAYSIZE( NAVITEM_PREFIX ) - 1;
return ( cchInvariant > cchPrefix &&
CompareString( CSTR_INVARIANT, 0, NAVITEM_PREFIX, cchPrefix, pszInvarian, cchPrefix ) == CSTR_EQUAL );
}
static HRESULT Navigation_GetServiceInt( HWND hLibrary, HNAVITEM hItem, OmService **service )
{
WCHAR szBuffer[ 64 ] = { 0 };
if ( service == NULL )
return E_POINTER;
*service = NULL;
if ( NULL == hLibrary || NULL == hItem )
return E_INVALIDARG;
NAVITEM itemInfo = {0};
itemInfo.cbSize = sizeof( NAVITEM );
itemInfo.hItem = hItem;
itemInfo.pszInvariant = szBuffer;
itemInfo.cchInvariantMax = ARRAYSIZE( szBuffer );
itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT;
if ( FALSE == MLNavItem_GetInfo( hLibrary, &itemInfo ) )
return E_FAIL;
if ( FALSE == Navigation_CheckInvariantName( szBuffer ) )
return E_NAVITEM_UNKNOWN;
*service = (OmService *)itemInfo.lParam;
(*service)->AddRef();
return S_OK;
}
HRESULT Navigation_GetService( HNAVITEM hItem, OmService **service )
{
return Navigation_GetServiceInt( plugin.hwndLibraryParent, hItem, service );
}
static HRESULT Navigation_CreateView( HNAVITEM hItem, HWND hParent, HWND *hView )
{
if ( NULL == hView )
return E_POINTER;
*hView = NULL;
if ( hItem == NULL || hParent == NULL )
return E_INVALIDARG;
HRESULT hr;
OmService *service;
hr = Navigation_GetServiceInt( plugin.hwndLibraryParent, hItem, &service );
if ( SUCCEEDED( hr ) )
{
if ( service->GetId() == SERVICE_PODCAST )
service->SetUrl( serviceUrl[ 0 ] ? serviceUrl : L"https://client.winamp.com/podcasts" );
hr = service->CreateView( hParent, hView );
service->Release();
}
return hr;
}
static void Navigation_OnDestroy()
{
if ( OMBROWSERMNGR != NULL )
OMBROWSERMNGR->Finish();
}
HNAVITEM Navigation_FindService( UINT serviceId, HNAVITEM hStart, OmService **serviceOut )
{
HWND hLibrary = plugin.hwndLibraryParent;
INT cchPrefix = ARRAYSIZE( NAVITEM_PREFIX ) - 1;
WCHAR szBuffer[256] = {0};
NAVITEM itemInfo = {0};
itemInfo.cbSize = sizeof( itemInfo );
itemInfo.mask = NIMF_TEXTINVARIANT | NIMF_PARAM;
itemInfo.cchInvariantMax = ARRAYSIZE( szBuffer );
itemInfo.pszInvariant = szBuffer;
if ( hStart == NULL )
hStart = MLNavCtrl_GetFirst( hLibrary );
itemInfo.hItem = hStart;
while ( itemInfo.hItem != NULL )
{
if ( FALSE != MLNavItem_GetInfo( hLibrary, &itemInfo ) &&
CSTR_EQUAL == CompareString( CSTR_INVARIANT, NORM_IGNORECASE, itemInfo.pszInvariant, cchPrefix,
NAVITEM_PREFIX, cchPrefix ) )
{
OmService *service = (OmService *)itemInfo.lParam;
if ( service != NULL && service->GetId() == serviceId )
{
if ( serviceOut != NULL )
{
*serviceOut = service;
service->AddRef();
}
return itemInfo.hItem;
}
}
itemInfo.hItem = MLNavItem_GetNext( hLibrary, itemInfo.hItem );
}
if ( serviceOut != NULL )
*serviceOut = NULL;
return NULL;
}
int downloadsViewLoaded = -1;
static int Navigation_CheckDownloadsView()
{
if ( downloadsViewLoaded == -1 )
{
pluginMessage p = { ML_MSG_DOWNLOADS_VIEW_LOADED, 0, 0, 0 };
downloadsViewLoaded = SENDMLIPC( plugin.hwndLibraryParent, ML_IPC_SEND_PLUGIN_MESSAGE, (WPARAM)&p );
}
return downloadsViewLoaded;
}
HRESULT Navigation_ShowService( UINT serviceId, INT showMode )
{
if ( serviceId == SERVICE_DOWNLOADS && Navigation_CheckDownloadsView() )
return S_OK;
Nullsoft::Utility::AutoLock lock3( navigationLock );
HNAVITEM hRoot = Navigation_FindService( SERVICE_PODCAST, NULL, NULL );
if( hRoot == NULL )
return E_UNEXPECTED;
switch ( serviceId )
{
case SERVICE_SUBSCRIPTION:
if ( SHOWMODE_AUTO == showMode )
return E_INVALIDARG;
break;
case SERVICE_DOWNLOADS:
if ( SHOWMODE_AUTO == showMode )
{
Nullsoft::Utility::AutoLock lock1( downloadedFiles.downloadedLock );
Nullsoft::Utility::AutoLock lock2( downloadStatus.statusLock );
showMode = ( 0 != downloadedFiles.downloadList.size() || 0 != downloadStatus.downloads.size() ) ? SHOWMODE_SHOW : SHOWMODE_HIDE;
}
break;
default:
return E_INVALIDARG;
}
if ( showMode != SHOWMODE_HIDE && showMode != SHOWMODE_SHOW )
return E_INVALIDARG;
HWND hLibrary = plugin.hwndLibraryParent;
MLNavCtrl_BeginUpdate( hLibrary, NUF_LOCK_TOP );
OmService *service;
HNAVITEM hItem = MLNavItem_GetChild( hLibrary, hRoot );
hItem = Navigation_FindService( serviceId, hItem, &service );
HRESULT hr = S_OK;
if ( showMode == SHOWMODE_HIDE )
{
if ( hItem == NULL )
hr = S_FALSE;
else if ( MLNavCtrl_DeleteItem( hLibrary, hItem ) == FALSE )
hr = E_FAIL;
}
else
{
if ( hItem != NULL )
hr = S_FALSE;
else
{
switch ( serviceId )
{
case SERVICE_SUBSCRIPTION:
hr = OmService::CreateLocal( SERVICE_SUBSCRIPTION, MAKEINTRESOURCE( IDS_SUBSCRIPTIONS ), MAKEINTRESOURCE( IDR_SUBSCRIPTION_ICON ), SubscriptionView_Create, &service );
break;
case SERVICE_DOWNLOADS:
hr = OmService::CreateLocal( SERVICE_DOWNLOADS, MAKEINTRESOURCE( IDS_DOWNLOADS ), MAKEINTRESOURCE( IDR_DOWNLOAD_ICON ), DownloadDialog_Create, &service );
break;
default:
hr = E_UNEXPECTED;
break;
}
if ( SUCCEEDED( hr ) )
{
if ( Navigation_CreateItem( hLibrary, hRoot, service ) == NULL )
hr = E_FAIL;
}
}
}
if ( service != NULL )
service->Release();
MLNavCtrl_EndUpdate( hLibrary );
return hr;
}
BOOL Navigation_Initialize( void )
{
HNAVITEM hParent = NULL;
OmService *service = NULL;
HWND hLibrary = plugin.hwndLibraryParent;
MLNavCtrl_BeginUpdate( hLibrary, NUF_LOCK_TOP );
if ( SUCCEEDED( OmService::CreateRemote( SERVICE_PODCAST, MAKEINTRESOURCE( IDS_PODCAST_DIRECTORY ),
MAKEINTRESOURCE( IDR_DISCOVER_ICON ),
( serviceUrl[ 0 ] ? serviceUrl : L"https://client.winamp.com/podcasts" ), &service ) ) )
{
service->SetFlags( OmService::flagRoot, OmService::flagRoot );
hParent = Navigation_CreateItem( hLibrary, hParent, service );
service->Release();
}
if ( hParent != NULL )
{
Navigation_ShowService( SERVICE_SUBSCRIPTION, SHOWMODE_SHOW );
//Navigation_ShowService(SERVICE_DOWNLOADS, SHOWMODE_AUTO);
}
MLNavCtrl_EndUpdate( hLibrary );
return TRUE;
}
static void Navigation_OnDeleteItem( HNAVITEM hItem )
{
if ( hItem == NULL )
return;
HWND hLibrary = plugin.hwndLibraryParent;
if ( hLibrary == NULL )
return;
WCHAR szBuffer[64] = {0};
NAVITEM itemInfo = {0};
itemInfo.cbSize = sizeof( itemInfo );
itemInfo.hItem = hItem;
itemInfo.pszInvariant = szBuffer;
itemInfo.cchInvariantMax = ARRAYSIZE( szBuffer );
itemInfo.mask = NIMF_PARAM | NIMF_TEXTINVARIANT | NIMF_IMAGE;
if ( MLNavItem_GetInfo( hLibrary, &itemInfo ) != FALSE && Navigation_CheckInvariantName( szBuffer ) != FALSE )
{
OmService *service = (OmService *)itemInfo.lParam;
itemInfo.mask = NIMF_PARAM;
itemInfo.lParam = 0L;
MLNavItem_SetInfo( hLibrary, &itemInfo );
service->Release();
}
}
BOOL Navigation_ProcessMessage( INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result )
{
if (msg < ML_MSG_TREE_BEGIN || msg > ML_MSG_TREE_END)
return FALSE;
switch ( msg )
{
case ML_MSG_TREE_ONCREATEVIEW:
{
HWND hView = NULL;
HRESULT hr = Navigation_CreateView( Navigation_GetMessageItem( msg, param1 ), (HWND)param2, &hView );
*result = ( SUCCEEDED( hr ) ) ? (INT_PTR)hView : NULL;
return TRUE;
}
case ML_MSG_NAVIGATION_ONDESTROY:
Navigation_OnDestroy();
return TRUE;
case ML_MSG_NAVIGATION_ONDELETE:
Navigation_OnDeleteItem( Navigation_GetMessageItem( msg, param1 ) );
return TRUE;
}
return FALSE;
}

View File

@ -0,0 +1,24 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_NAVIGATION_HEADER
#define NULLSOFT_PODCAST_PLUGIN_NAVIGATION_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
typedef LPVOID HNAVITEM;
class OmService;
BOOL Navigation_Initialize(void);
BOOL Navigation_ProcessMessage(INT msg, INT_PTR param1, INT_PTR param2, INT_PTR param3, INT_PTR *result);
#define SHOWMODE_HIDE ((INT)0)
#define SHOWMODE_SHOW ((INT)1)
#define SHOWMODE_AUTO ((INT)-1)
HRESULT Navigation_ShowService(UINT serviceId, INT showMode);
HNAVITEM Navigation_FindService(UINT serviceId, HNAVITEM hStart, OmService **serviceOut);
#endif //NULLSOFT_PODCAST_PLUGIN_NAVIGATION_HEADER

View File

@ -0,0 +1,15 @@
#include "resource.h"
/////////////////////////////////////////////////////////////////////////////
//
// Data
//
IDR_DISCOVER_ICON RCDATA
".\\resources\\discoverIcon.png"
IDR_DOWNLOAD_ICON RCDATA
".\\resources\\downloadIcon.png"
IDR_SUBSCRIPTION_ICON RCDATA
".\\resources\\subscriptionIcon.png"
IDR_MEDIA_ICON RCDATA
".\\resources\\mediaIcon.png"
IDR_TEXT_ICON RCDATA
".\\resources\\textIcon.png"

View File

@ -0,0 +1,220 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ml_podcast.rc
//
#define IDS_NULLSOFT_PODCAST 0
#define IDS_PODCASTS 1
#define IDS_DOWNLOADING_KB_COMPLETE 2
#define IDS_DOWNLOADING_KB_PROGRESS 3
#define IDS_SUBSCRIPTIONS 4
#define IDS_DOWNLOADS 5
#define IDS_DOWNLOADING 6
#define IDS_CHOOSE_FOLDER 7
#define IDS_ALREADY_SUBSCRIBED 8
#define IDD_PODCAST 9
#define IDS_FILE_NOT_FOUND 9
#define IDS_CONNECTION_TIMED_OUT 10
#define IDS_ERROR_PARSING_XML 11
#define IDS_INVALID_RSS_FEED 12
#define IDS_NO_JNETLIB 13
#define IDS_JNETLIB_MISSING 14
#define IDS_NO_EXPAT 15
#define IDS_EXPAT_MISSING 16
#define IDS_CONNECTION_RESET 17
#define IDS_ERROR_SUBSCRIBING_TO_PODCAST 18
#define IDS_UPD_MANUALLY 19
#define IDS_UPD_WEEK 20
#define IDS_UPD_DAY 21
#define IDS_UPD_12HRS 22
#define IDS_UPD_6HRS 23
#define IDS_UPD_3HRS 24
#define IDS_UPD_HOUR 25
#define IDS_UPD_30MINS 26
#define IDS_ERROR_FYEO 27
#define IDS_WDAY_SUN 28
#define IDS_WDAY_MON 29
#define IDS_WDAY_TUE 30
#define IDS_WDAY_WED 31
#define IDS_WDAY_THU 32
#define IDS_WDAY_FRI 33
#define IDS_WDAY_SAT 34
#define IDS_MONTH_JAN 35
#define IDS_MONTH_FEB 36
#define IDS_MONTH_MAR 37
#define IDS_MONTH_APR 38
#define IDS_MONTH_MAY 39
#define IDS_MONTH_JUN 40
#define IDS_MONTH_JUL 41
#define IDS_MONTH_AUG 42
#define IDS_MONTH_SEP 43
#define IDS_MONTH_OCT 44
#define IDS_MONTH_NOV 45
#define IDS_MONTH_DEC 46
#define IDS_RECEIVING_UPDATES_FOR 47
#define IDS_GOT_NEW_ITEMS_FOR 48
#define IDS_CHANNEL_ALREADY_PRESENT 49
#define IDS_DUPLICATE_CHANNEL 50
#define IDS_ERROR_ADDING_URL 51
#define IDS_ERROR_PARSING_XML_FROM_SERVER 52
#define IDS_LINK_HAS_NO_RSS_INFO 53
#define IDS_INVALID_LINK 54
#define IDS_CONNECTION_RESET_BY_PEER 55
#define IDS_DOWNLOADING_PERCENT 56
#define IDS_CHANNEL 57
#define IDS_ITEM 58
#define IDS_PROGRESS 59
#define IDS_PATH 60
#define IDS_PERM_DELETE_ARE_YOU_SURE 61
#define IDS_PERM_DELETE_THESE_ARE_YOU_SURE 62
#define IDS_DELETION 63
#define IDS_CLEAR_ALL_FINISHED_DOWNLOADS 64
#define IDS_CLEAN_UP_LIST 65
#define IDS_DONE 66
#define IDS_SAVE 67
#define IDS_REQUIRES_INTERNET_CONNECTION_ENSURE_CONNECTION 68
#define IDS_ADD_TO_DOWNLOADS 69
#define IDS_NO_MEDIA_TO_DOWNLOAD 70
#define IDS_TEXT_ARTICLE 71
#define IDS_DATE_ADDED 72
#define IDS_MEDIA_PRESENT 73
#define IDS_SURE_YOU_WANT_TO_REMOVE_THIS 74
#define IDS_CONFIRM 75
#define IDS_CANCEL_DOWNLOADS_AND_QUIT 76
#define IDS_CONFIRM_QUIT 77
#define IDS_ARTICLE_WITH_MEDIA 78
#define IDS_PODCASTS_DIRECTORY 79
#define IDS_PODCAST_DIRECTORY 79
#define IDS_DELETEFAILED 80
#define IDS_STRING81 81
#define IDS_SORRY 81
#define IDS_EDIT_CHANNEL 82
#define IDS_ATD_NEVER 83
#define IDS_ATD_LASTONE 84
#define IDS_ATD_LASTTWO 85
#define IDS_ATD_LASTTHREE 86
#define IDS_ATD_LASTFIVE 87
#define IDD_PREFERENCES 104
#define IDD_ADDURL 105
#define IDD_DOWNLOADS 107
#define IDB_ICON_TEXT 110
#define IDB_BITMAP1 111
#define IDB_ICON_MEDIA 111
#define IDR_MENU1 112
#define IDB_BITMAP2 114
#define IDB_TREEIMAGE_DOWNLOAD 114
#define IDB_BITMAP3 115
#define IDB_TREEIMAGE_SUBSCRIPTION 115
#define IDB_BITMAP4 116
#define IDB_TREEIMAGE_DISCOVER 116
#define IDS_PODCAST_SUBSCRIPTION_HEADER 120
#define IDS_PODCAST_SUBSCRIPTION_PROMP 121
#define IDR_VIEW_DOWNLOAD_ACCELERATORS 122
#define IDS_MEDIA_TIME 125
#define IDS_EPISODE_INFO 126
#define IDS_MEDIA_SIZE 127
#define IDS_BYTES 128
#define IDS_KIB 129
#define IDS_MIB 130
#define IDS_GIB 131
#define IDC_CUSTOM 1000
#define IDC_CHANNELLIST 1002
#define IDC_CHANNELLIST2 1003
#define IDC_EPISODE_INFO 1003
#define IDC_ITEMLIST 1006
#define IDC_DESCRIPTION 1008
#define IDC_FEEDLIST 1009
#define IDC_NEW 1010
#define IDC_DELETE 1011
#define IDC_REFRESH 1012
#define IDC_EDITDESCRIPTION 1013
#define IDC_EDITURL 1014
#define IDC_UPDATETIME 1017
#define IDC_AUTODOWNLOAD 1018
#define IDC_DEFAULTUPDATETIME 1019
#define IDC_DEFAULTAUTODOWNLOAD 1020
#define IDC_APPLYDEFAULTS 1021
#define IDC_LOCATION 1022
#define IDC_LOCATIONBROWSE 1024
#define IDC_ADDURLEDIT 1027
#define IDC_ADDURLBUTTON 1028
#define IDC_LIST1 1029
#define IDC_DOWNLOADLIST 1029
#define IDC_STATICDESCRIPTION 1030
#define IDC_STATICURL 1031
#define IDC_STATICUPDATE 1032
#define IDC_DOWNLOADGROUP 1033
#define IDC_STATICAUTODOWNLOAD 1035
#define IDC_FEEDSGROUP 1036
#define IDC_DEFAULTSGROUP 1037
#define IDC_STATICUPDATEEVERY 1038
#define IDC_STATICDEFAULTAUTODOWNLOAD 1039
#define IDC_ADD 1044
#define IDC_EDIT 1045
#define IDC_PLAY 1047
#define IDC_ADDURL 1047
#define IDC_ENQUEUE 1048
#define IDC_USEDEFAULTS 1048
#define IDC_VISIT 1049
#define IDC_USECUSTOM 1049
#define IDC_DOWNLOAD 1050
#define IDC_CUSTOMGROUP 1050
#define IDC_BUTTON1 1051
#define IDC_CANCEL 1051
#define IDC_BROWSE 1051
#define IDC_ENQUEUE2 1051
#define IDC_FINDNEW 1051
#define IDC_UPDATELIST 1052
#define IDC_VDIV 1054
#define IDC_HDIV1 1055
#define IDC_HDIV 1055
#define IDC_BROWSER 1057
#define IDC_STATUS 1058
#define IDC_EDIT1 1059
#define IDC_DOWNLOADLOCATION 1059
#define IDC_SETTINGSBOX 1060
#define IDC_CLEANUP 1060
#define IDC_CHECK1 1062
#define IDC_UPDATEONLAUNCH 1062
#define IDC_STATIC_UPDATEEVERY 1064
#define IDC_PLAYACTION 1065
#define IDC_ENQUEUEACTION 1066
#define IDC_AUTODOWNLOADLIST 1067
#define IDC_STATIC_AUTODOWNLOAD 1068
#define IDC_DIRECTORYURL 1069
#define IDR_DISCOVER_ICON 20000
#define IDR_DOWNLOAD_ICON 20001
#define IDR_SUBSCRIPTION_ICON 20002
#define IDR_MEDIA_ICON 20003
#define IDR_TEXT_ICON 20004
#define IDM_CHANNELS 40006
#define IDC_REFRESHALL 40008
#define ID_PLAYMEDIA_IDC 40018
#define IDC_REMOVE 40024
#define ID_DOWNLOADS_SENDTO 40025
#define ID_Menu 40026
#define IDC_INFOBOX 40028
#define IDC_SELECTALL 40030
#define ID_DOWNLOADS_SENDTO40031 40031
#define ID_NAVIGATION_PREFERENCES 40032
#define ID_NAVIGATION_HELP 40033
#define ID_DOWNLOADS_EXPLORERITEMFOLDER 40034
#define IDC_EXPLORERITEMFOLDER 40034
#define ID_ENQUEUE 40036
#define ID_SUBSCRIPTIONNAVIGATION_PREFERENCE 40043
#define ID_SUBSCRIPTIONNAVIGATION_DIRECTORY 40044
#define ID_NAVIGATION_DIRECTORY 40045
#define ID_SUBSCRIPTIONNAVIGATION_HELP 40046
#define ID_NAVIGATION_REFRESHALL 40047
#define ID_EPISODE_SELECTALL 40048
#define IDS_PLUGIN_NAME 65534
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 133
#define _APS_NEXT_COMMAND_VALUE 40052
#define _APS_NEXT_CONTROL_VALUE 1068
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

View File

@ -0,0 +1,273 @@
#include "main.h"
#include "./service.h"
#include "api__ml_wire.h"
#include "./util.h"
#include "./resource.h"
#include "./externalCOM.h"
#include "../winamp/wa_ipc.h"
#include <strsafe.h>
#define IS_INVALIDISPATCH(__disp) (((IDispatch *)1) == (__disp) || NULL == (__disp))
OmService::OmService( UINT nId ) : id( nId )
{}
OmService::~OmService()
{
Plugin_FreeResString( name );
Plugin_FreeResString( url );
Plugin_FreeResString( icon );
}
HRESULT OmService::CreateRemote( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, OmService **instance )
{
if ( instance == NULL )
return E_POINTER;
*instance = NULL;
if ( nId == 0 || pszName == NULL )
return E_INVALIDARG;
OmService *service = new OmService( nId );
if ( service == NULL )
return E_OUTOFMEMORY;
service->SetName( pszName );
service->SetIcon( pszIcon );
service->SetUrl( pszUrl );
*instance = service;
return S_OK;
}
HRESULT OmService::CreateLocal( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, SVCWNDCREATEPROC windowCreator, OmService **instance )
{
if ( instance == NULL )
return E_POINTER;
*instance = NULL;
if ( nId == 0 || pszName == NULL )
return E_INVALIDARG;
OmService *service = new OmService( nId );
if ( service == NULL )
return E_OUTOFMEMORY;
service->SetFlags( flagLocal, flagLocal );
service->SetName( pszName );
service->SetIcon( pszIcon );
service->SetWindowCreator( windowCreator );
*instance = service;
return S_OK;
}
size_t OmService::AddRef()
{
return _ref.fetch_add( 1 );
}
size_t OmService::Release()
{
if ( _ref.load() == 0 )
return _ref.load();
LONG r = _ref.fetch_sub( 1 );
if ( r == 0 )
delete( this );
return r;
}
int OmService::QueryInterface( GUID interface_guid, void **object )
{
if ( object == NULL )
return E_POINTER;
if ( IsEqualIID( interface_guid, IFC_OmService ) )
*object = static_cast<ifc_omservice *>( this );
else
{
*object = NULL;
return E_NOINTERFACE;
}
if ( *object == NULL )
return E_UNEXPECTED;
AddRef();
return S_OK;
}
unsigned int OmService::GetId()
{
return id;
}
HRESULT OmService::GetName( wchar_t *pszBuffer, int cchBufferMax )
{
return Plugin_CopyResString( pszBuffer, cchBufferMax, name );
}
HRESULT OmService::GetUrl( wchar_t *pszBuffer, int cchBufferMax )
{
return Plugin_CopyResString( pszBuffer, cchBufferMax, url );
}
HRESULT OmService::GetIcon( wchar_t *pszBuffer, int cchBufferMax )
{
if ( icon != NULL && IS_INTRESOURCE( icon ) )
{
WCHAR szPath[ 2 * MAX_PATH ] = { 0 };
if ( GetModuleFileName( plugin.hDllInstance, szPath, ARRAYSIZE( szPath ) ) == 0 )
return E_FAIL;
return StringCchPrintf( pszBuffer, cchBufferMax, L"res://%s/#%d/#%d", szPath, RT_RCDATA, icon );
}
return StringCchCopyEx( pszBuffer, cchBufferMax, icon, NULL, NULL, STRSAFE_IGNORE_NULLS );
}
HRESULT OmService::GetExternal( IDispatch **ppDispatch )
{
if ( ppDispatch == NULL )
return E_POINTER;
*ppDispatch = NULL;
HWND hWinamp = plugin.hwndWinampParent;
if ( hWinamp == NULL )
return E_UNEXPECTED;
//////*ppDispatch = (IDispatch*)SENDWAIPC(hWinamp, IPC_GET_DISPATCH_OBJECT, 0);
WCHAR szBuffer[ 64 ] = { 0 };
if ( SUCCEEDED( StringCchPrintfW( szBuffer, ARRAYSIZE( szBuffer ), L"%u", id ) ) )
*ppDispatch = (IDispatch *) SENDWAIPC( hWinamp, IPC_JSAPI2_GET_DISPATCH_OBJECT, (WPARAM) szBuffer );
if (IS_INVALIDISPATCH(*ppDispatch) && FAILED(ExternalCOM::CreateInstance((ExternalCOM**)ppDispatch)))
{
*ppDispatch = NULL;
return E_FAIL;
}
return S_OK;
}
HRESULT OmService::SetName( LPCWSTR pszName )
{
Plugin_FreeResString( name );
name = Plugin_DuplicateResString( pszName );
return S_OK;
}
HRESULT OmService::SetUrl( LPCWSTR pszUrl )
{
Plugin_FreeResString( url );
url = Plugin_DuplicateResString( pszUrl );
return S_OK;
}
HRESULT OmService::SetIcon( LPCWSTR pszIcon )
{
Plugin_FreeResString( icon );
icon = Plugin_DuplicateResString( pszIcon );
return S_OK;
}
void OmService::SetFlags( UINT mask, UINT newFlags )
{
flags = ( flags & ~mask ) | ( mask & newFlags );
}
UINT OmService::GetFlags( void )
{
return flags;
}
HRESULT OmService::SetWindowCreator( SVCWNDCREATEPROC proc )
{
windowCreator = proc;
return S_OK;
}
HRESULT OmService::GetWindowCreator( SVCWNDCREATEPROC *proc )
{
if ( proc == NULL )
return E_INVALIDARG;
*proc = windowCreator;
return S_OK;
}
HRESULT OmService::CreateView( HWND hParent, HWND *hView )
{
if ( hView == NULL )
return E_POINTER;
*hView = NULL;
HRESULT hr = S_OK;
if ( ( flagLocal & flags ) != 0 )
{
if ( windowCreator != NULL )
{
*hView = windowCreator( hParent, this );
if ( *hView == NULL )
hr = E_FAIL;
}
else
hr = E_INVALIDARG;
}
else
{
if ( OMBROWSERMNGR != NULL )
{
hr = OMBROWSERMNGR->Initialize( NULL, plugin.hwndWinampParent );
if ( SUCCEEDED( hr ) )
hr = OMBROWSERMNGR->CreateView( this, hParent, NULL, 0, hView );
}
else
hr = E_UNEXPECTED;
}
return hr;
}
#define CBCLASS OmService
START_DISPATCH;
CB( ADDREF, AddRef )
CB( RELEASE, Release )
CB( QUERYINTERFACE, QueryInterface )
CB( API_GETID, GetId )
CB( API_GETNAME, GetName )
CB( API_GETURL, GetUrl )
CB( API_GETICON, GetIcon )
CB( API_GETEXTERNAL, GetExternal )
END_DISPATCH;
#undef CBCLASS

View File

@ -0,0 +1,69 @@
#ifndef NULLSOFT_PODCAST_PLUGIN_SERVICE_HEADER
#define NULLSOFT_PODCAST_PLUGIN_SERVICE_HEADER
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <wtypes.h>
#include <atomic>
#include "../omBrowser/ifc_omservice.h"
class OmService;
typedef HWND (CALLBACK *SVCWNDCREATEPROC)(HWND /*hParent*/, OmService* /*service*/);
class OmService : public ifc_omservice
{
public:
typedef enum
{
flagRoot = 0x00000001,
flagLocal = 0x00000002,
} Flags;
protected:
OmService( UINT nId );
~OmService();
public:
static HRESULT CreateRemote( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, LPCWSTR pszUrl, OmService **instance );
static HRESULT CreateLocal( UINT nId, LPCWSTR pszName, LPCWSTR pszIcon, SVCWNDCREATEPROC windowCreator, OmService **instance );
/* Dispatchable */
size_t AddRef();
size_t Release();
int QueryInterface( GUID interface_guid, void **object );
/* ifc_omservice */
unsigned int GetId();
HRESULT GetName( wchar_t *pszBuffer, int cchBufferMax );
HRESULT GetUrl( wchar_t *pszBuffer, int cchBufferMax );
HRESULT GetExternal( IDispatch **ppDispatch );
HRESULT GetIcon( wchar_t *pszBuffer, int cchBufferMax );
HRESULT SetName( LPCWSTR pszName );
HRESULT SetUrl( LPCWSTR pszUrl );
HRESULT SetIcon( LPCWSTR pszIcon );
void SetFlags( UINT mask, UINT newFlags );
UINT GetFlags( void );
HRESULT SetWindowCreator( SVCWNDCREATEPROC proc );
HRESULT GetWindowCreator( SVCWNDCREATEPROC *proc );
HRESULT CreateView( HWND hParent, HWND *hView );
protected:
RECVS_DISPATCH;
std::atomic<std::size_t> _ref = 1;
UINT id = 0;
LPWSTR name = NULL;
LPWSTR url = NULL;
SVCWNDCREATEPROC windowCreator = NULL;
LPWSTR icon = NULL;
UINT flags = 0;
};
#endif //NULLSOFT_PODCAST_PLUGIN_SERVICE_HEADER

Some files were not shown because too many files have changed in this diff Show More