///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using JetBrains.Omea.Base;
using JetBrains.Omea.Net;
using JetBrains.Omea.OpenAPI;
namespace JetBrains.Omea.Nntp
{
///
/// nntp authentication
///
internal class NntpAuthenticateUnit: AsciiProtocolUnit
{
public NntpAuthenticateUnit( string username, string password )
{
_username = username;
_password = password;
}
public bool Succeeded
{
get { return _succeeded; }
}
public string ResponseLine
{
get { return _responseLine; }
}
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
AsciiSendLineGetLineUnit sendUsernameUnit =
new AsciiSendLineGetLineUnit( "authinfo user " + _username );
sendUsernameUnit.Finished += new AsciiProtocolUnitDelegate( sendUsernameUnit_Finished );
StartUnit( sendUsernameUnit, connection );
}
private void sendUsernameUnit_Finished( AsciiProtocolUnit unit )
{
AsciiSendLineGetLineUnit sendUsernameUnit = (AsciiSendLineGetLineUnit) unit;
if( sendUsernameUnit.LineSent )
{
_responseLine = sendUsernameUnit.ResponseLine;
if( _responseLine != null )
{
if( _responseLine.StartsWith( "381" ) ) // password required
{
AsciiSendLineGetLineUnit sendPasswordUnit =
new AsciiSendLineGetLineUnit( "authinfo pass " + _password );
sendPasswordUnit.Finished += new AsciiProtocolUnitDelegate( sendPasswordUnit_Finished );
StartUnit( sendPasswordUnit, _connection );
return;
}
if( _responseLine.StartsWith( "281" ) ) // password not required (wonder if this can occur)
{
_succeeded = true;
}
}
}
FireFinished();
}
private void sendPasswordUnit_Finished( AsciiProtocolUnit unit )
{
AsciiSendLineGetLineUnit sendPasswordUnit = (AsciiSendLineGetLineUnit) unit;
if( sendPasswordUnit.LineSent )
{
_responseLine = sendPasswordUnit.ResponseLine;
if( _responseLine != null && _responseLine.StartsWith( "281" ) )
{
_succeeded = true;
}
}
FireFinished();
}
private string _username;
private string _password;
private bool _succeeded;
private string _responseLine;
private AsciiTcpConnection _connection;
}
///
/// downloading group names
///
internal class NntpDownloadGroupsUnit: AsciiProtocolUnit
{
public NntpDownloadGroupsUnit( IResource server, bool refresh, JobPriority priority )
{
Interlocked.Increment( ref NntpPlugin._deliverNewsUnitCount );
_serverResource = new ServerResource( server );
Core.UIManager.GetStatusWriter( typeof( NntpDownloadGroupsUnit ),
StatusPane.Network ).ShowStatus( "Downloading groups from " + _serverResource.DisplayName + "..." );
_priority = priority;
_nntpCmd = "list";
if( !refresh )
{
DateTime lastUpdated = _serverResource.LastUpdateTime;
if( lastUpdated > DateTime.MinValue )
{
_nntpCmd = "newgroups " + ParseTools.NNTPDateString( lastUpdated );
}
}
_count = 0;
_responseChecked = false;
_groupList = new ArrayList();
_groupListLock = new SpinWaitLock();
_flushGroupListDelegate = new MethodInvoker( FlushGroupList );
}
///
/// A group downloaded by a unit
///
public delegate void DroupDownloadedDelegate( string group, NntpDownloadGroupsUnit unit );
public event DroupDownloadedDelegate GroupDownloaded;
///
/// Number of currently downloaded groups
///
public int Count
{
get { return _count; }
}
public ServerResource Server
{
get { return _serverResource; }
}
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
_enumGroupsUnit = new AsciiSendLineAndApplyMethodUnit(
_nntpCmd, ".\r\n", new LineDelegate( ProcessGroupLine ) );
_enumGroupsUnit.Finished += new AsciiProtocolUnitDelegate( enumGroupsUnit_Finished );
_startUpdateDate = DateTime.Now;
StartUnit( _enumGroupsUnit, connection );
}
protected override void FireFinished()
{
FlushGroupList();
Interlocked.Decrement( ref NntpPlugin._deliverNewsUnitCount );
if( _enumGroupsUnit != null )
{
_enumGroupsUnit.Finished -= new AsciiProtocolUnitDelegate( enumGroupsUnit_Finished );
FireFinished( _enumGroupsUnit );
}
GroupDownloaded = null;
Core.UIManager.GetStatusWriter( typeof( NntpDownloadGroupsUnit ), StatusPane.Network ).ClearStatus();
base.FireFinished();
}
private void ProcessGroupLine( string line )
{
if( _serverResource.Resource.IsDeleted )
{
FireFinished();
}
else
{
if( !_responseChecked )
{
_responseChecked = true;
if( !line.StartsWith( "215" ) && !line.StartsWith( "231" ) && !line.StartsWith( "221" ) )
{
if( line.Length > 4 )
{
new ResourceProxy( _serverResource.Resource ).SetPropAsync(
Core.Props.LastError, line.Substring( 4 ).TrimEnd( '\r', '\n' ) );
Core.NetworkAP.QueueJob( new MethodInvoker( _connection.Close ) );
}
FireFinished();
}
}
else
{
int index = line.IndexOf( ' ' );
if( index < 0 )
{
index = line.Length;
while( index > 0 )
{
char c = line[ index - 1 ];
if( c != '\r' && c != '\n' )
{
break;
}
--index;
}
}
if( index > 0 )
{
string group = line.Substring( 0, index );
bool needFlush = false;
_groupListLock.Enter();
try
{
_groupList.Add( group );
needFlush = _groupList.Count > 16;
}
finally
{
_groupListLock.Exit();
}
if( needFlush )
{
FlushGroupList();
}
++_count;
if( GroupDownloaded != null )
{
GroupDownloaded( group, this );
}
}
}
}
}
private void FlushGroupList()
{
IAsyncProcessor ap = Core.ResourceAP;
if( !ap.IsOwnerThread )
{
ap.QueueJob( _priority, _flushGroupListDelegate );
}
else
{
_groupListLock.Enter();
try
{
foreach( string group in _groupList )
{
_serverResource.AddGroup( group );
}
_groupList.Clear();
}
finally
{
_groupListLock.Exit();
}
}
}
private void enumGroupsUnit_Finished( AsciiProtocolUnit unit )
{
_serverResource.LastUpdateTime = _startUpdateDate;
_enumGroupsUnit = null;
FireFinished();
}
private ServerResource _serverResource;
private JobPriority _priority;
private string _nntpCmd;
private AsciiTcpConnection _connection;
private AsciiSendLineAndApplyMethodUnit _enumGroupsUnit;
private int _count;
private bool _responseChecked;
private DateTime _startUpdateDate;
private ArrayList _groupList;
private SpinWaitLock _groupListLock;
private MethodInvoker _flushGroupListDelegate;
}
///
/// set current group
///
internal class NntpSetGroupUnit: AsciiProtocolUnit
{
public NntpSetGroupUnit( NewsgroupResource group )
{
_group = group;
}
public string ResponseLine
{
get { return _responseLine; }
}
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
AsciiSendLineGetLineUnit setGroupUnit = new AsciiSendLineGetLineUnit( "group " + _group.Name );
setGroupUnit.Finished += new AsciiProtocolUnitDelegate( setGroupUnit_Finished );
StartUnit( setGroupUnit, connection );
}
private void setGroupUnit_Finished( AsciiProtocolUnit unit )
{
AsciiSendLineGetLineUnit setGroupUnit = (AsciiSendLineGetLineUnit) unit;
_responseLine = setGroupUnit.ResponseLine;
string error = null;
if( _responseLine == null )
{
if( _connection.LastSocketException != null )
{
error = _connection.LastSocketException.Message;
}
else
{
error = "Newsgroup could not be processed";
}
}
else if( !_responseLine.StartsWith( "211" ) )
{
if( _responseLine.Length > 4 )
{
error = _responseLine.Substring( 4 );
}
else
{
error = "Newsgroup could not be processed";
}
}
ResourceProxy proxy = new ResourceProxy( _group.Resource );
if( error != null )
{
proxy.SetPropAsync( Core.Props.LastError, error );
}
else
{
proxy.DeletePropAsync( Core.Props.LastError );
}
FireFinished();
}
private NewsgroupResource _group;
private string _responseLine;
private AsciiTcpConnection _connection;
}
///
/// base class downloading article headers
/// override the SetArticleNumbersRange() factory method
///
internal abstract class NntpDownloadHeadersUnitBase: AsciiProtocolUnit
{
protected NntpDownloadHeadersUnitBase( NewsgroupResource group, JobPriority priority )
{
_group = group;
_priority = priority;
}
protected override void Start( AsciiTcpConnection connection )
{
Core.UIManager.GetStatusWriter( typeof( NntpDownloadHeadersUnitBase ),
StatusPane.Network ).ShowStatus( "Downloading headers from " + _group.DisplayName + "..." );
_connection = connection;
if( !_group.IsSubscribed )
{
FireFinished();
}
else
{
NntpSetGroupUnit downloadHeadersSetGroupUnit = new NntpSetGroupUnit( _group );
downloadHeadersSetGroupUnit.Finished += new AsciiProtocolUnitDelegate( downloadHeadersSetGroupUnit_Finished );
StartUnit( downloadHeadersSetGroupUnit, connection );
}
}
protected override void FireFinished()
{
if( _getHeadersUnit != null )
{
_getHeadersUnit.Finished -= new AsciiProtocolUnitDelegate( getHeadersUnit_Finished );
FireFinished( _getHeadersUnit );
}
Core.UIManager.GetStatusWriter(
typeof( NntpDownloadHeadersUnitBase ), StatusPane.Network ).ClearStatus();
base.FireFinished();
}
///
/// Factory method to set range of numbers of articles which we are going to download
/// When entering the method, firstArticle and lastArticle are equal to what the server
/// has responded on the GROUP command
///
protected abstract void SetArticleNumbersRange( ref int firstArticle, ref int lastArticle );
protected virtual int GetHeadersCount()
{
int count = _group.CountToDownloadAtTime;
if( count > _lastArticle - _firstArticle + 1 )
{
count = _lastArticle - _firstArticle + 1;
}
return count;
}
private void downloadHeadersSetGroupUnit_Finished( AsciiProtocolUnit unit )
{
NntpSetGroupUnit setGroupUnit = (NntpSetGroupUnit) unit;
string response = setGroupUnit.ResponseLine;
if( response != null )
{
while( response.StartsWith( "211" ) )
{
string[] parts = response.Split( ' ' );
_firstArticleCopy = _firstArticle = Int32.Parse( parts[ 2 ] );
_lastArticle = Int32.Parse( parts[ 3 ] );
SetArticleNumbersRange( ref _firstArticle, ref _lastArticle );
if( _lastArticle == 0 || _lastArticle < _firstArticle )
{
break;
}
_getHeadersUnit = new AsciiSendLineAndApplyMethodUnit(
"xover " + _firstArticle + '-' + _lastArticle, ".\r\n", new LineDelegate( ProcessHeadersLine ) );
_getHeadersUnit.Finished += new AsciiProtocolUnitDelegate( getHeadersUnit_Finished );
StartUnit( _getHeadersUnit, _connection );
return;
}
}
FireFinished();
}
private void ProcessHeadersLine( string line )
{
if( !_responseChecked )
{
_responseChecked = true;
if( !line.StartsWith( "224" ) )
{
FireFinished();
}
}
else
{
Core.ResourceAP.QueueJob(
_priority, "Creating news header", _createArticlesFromHeadersMethod, line, _group.Resource );
}
}
private void getHeadersUnit_Finished( AsciiProtocolUnit unit )
{
if( _getHeadersUnit.LineSent )
{
SetGroupNumbers();
}
_getHeadersUnit = null;
FireFinished();
}
private void SetGroupNumbers()
{
if( !Core.ResourceStore.IsOwnerThread() )
{
Core.ResourceAP.QueueJob(
_priority, "Updating newsgroup structure", new MethodInvoker( SetGroupNumbers ) );
}
else
{
IResource group = _group.Resource;
if( !group.IsDeleted )
{
group.BeginUpdate();
try
{
_group.LastArticle = _lastArticle;
_group.FirstArticle = _firstArticle;
if( _firstArticleCopy >= _group.FirstArticle )
{
group.SetProp( NntpPlugin._propNoMoreHeaders, true );
}
}
finally
{
group.EndUpdate();
}
}
}
}
protected NewsgroupResource _group;
private JobPriority _priority;
private int _firstArticle;
private int _firstArticleCopy;
private int _lastArticle;
private bool _responseChecked;
private AsciiSendLineAndApplyMethodUnit _getHeadersUnit;
private AsciiTcpConnection _connection;
private static CreateArticleFromHeadersDelegate _createArticlesFromHeadersMethod =
new CreateArticleFromHeadersDelegate( NewsArticleParser.CreateArticleFromHeaders );
}
///
/// download new headers
///
internal class NntpDownloadHeadersUnit : NntpDownloadHeadersUnitBase
{
public NntpDownloadHeadersUnit( NewsgroupResource group, JobPriority priority )
: base( group, priority ) {}
protected override void SetArticleNumbersRange( ref int firstArticle, ref int lastArticle )
{
int headersCount2Get = GetHeadersCount();
int currentLastArticle = _group.LastArticle;
// first time last article is not set
if( currentLastArticle == 0 )
{
currentLastArticle = lastArticle - headersCount2Get;
if( currentLastArticle < firstArticle - 1 )
{
currentLastArticle = firstArticle - 1;
}
}
else
{
if( firstArticle > currentLastArticle )
{
currentLastArticle = firstArticle - 1;
}
else if( _group.FirstArticle > lastArticle )
{
ResourceProxy proxy = new ResourceProxy( _group.Resource );
proxy.BeginUpdate();
try
{
proxy.DeleteProp( NntpPlugin._propFirstArticle );
proxy.DeleteProp( NntpPlugin._propLastArticle );
}
finally
{
proxy.EndUpdateAsync();
}
}
}
if( headersCount2Get < lastArticle - currentLastArticle )
{
lastArticle = currentLastArticle + headersCount2Get;
firstArticle = currentLastArticle + 1;
}
else
{
headersCount2Get = lastArticle - currentLastArticle;
if( headersCount2Get <= 0 )
{
lastArticle = 0;
}
else
{
firstArticle = currentLastArticle + 1;
}
}
}
}
///
/// download next headers
///
internal class NntpDownloadNextHeadersUnit: NntpDownloadHeadersUnitBase
{
public NntpDownloadNextHeadersUnit( NewsgroupResource group, JobPriority priority )
: base( group, priority ) {}
protected override void SetArticleNumbersRange( ref int firstArticle, ref int lastArticle )
{
int headersCount2Get = GetHeadersCount();
int currentFirstArticle = _group.FirstArticle;
// first time first article is not set
if( currentFirstArticle == Int32.MaxValue )
{
currentFirstArticle = lastArticle + 1;
}
if( headersCount2Get > currentFirstArticle - firstArticle )
{
headersCount2Get = currentFirstArticle - firstArticle;
}
if( headersCount2Get <= 0 )
{
lastArticle = 0;
}
else
{
lastArticle = currentFirstArticle - 1;
firstArticle = currentFirstArticle - headersCount2Get;
}
}
}
///
/// download all headers
///
internal class NntpDownloadAllHeadersUnit: NntpDownloadHeadersUnitBase
{
public NntpDownloadAllHeadersUnit( NewsgroupResource group, JobPriority priority )
: base( group, priority ) {}
protected override void SetArticleNumbersRange( ref int firstArticle, ref int lastArticle )
{
}
}
///
/// download an article
///
internal class NntpDownloadArticleUnit: AsciiProtocolUnit
{
public NntpDownloadArticleUnit( IResource article,
IResource group,
JobPriority priority,
bool setGroup )
{
_article = article;
_group = group;
_priority = priority;
_setGroup = setGroup;
}
public bool ArticleAvailable
{
get { return _articleAvailable; }
}
public IResource Article
{
get { return _article; }
}
public delegate void ProgressDelegate( NntpDownloadArticleUnit sender, string line );
public event ProgressDelegate OnProgress;
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
if( _setGroup )
{
NntpSetGroupUnit setGroupUnit = new NntpSetGroupUnit( new NewsgroupResource( _group ) );
setGroupUnit.Finished += new AsciiProtocolUnitDelegate( setGroupUnit_Finished );
StartUnit( setGroupUnit, connection );
}
else
{
GetArticleById();
}
}
protected override void FireFinished()
{
if( _getArticleUnit != null )
{
_getArticleUnit.Finished -= new AsciiProtocolUnitDelegate( getArticleUnit_Finished );
FireFinished( _getArticleUnit );
}
OnProgress = null;
if( !_articleAvailable )
{
Core.ResourceAP.QueueJob( _priority,
new SetErrorDelegate( SetError ), "Connection closed.", false );
}
base.FireFinished();
}
private void setGroupUnit_Finished( AsciiProtocolUnit unit )
{
NntpSetGroupUnit setGroupUnit = (NntpSetGroupUnit) unit;
string response = setGroupUnit.ResponseLine;
if( response == null || !response.StartsWith( "211" ) )
{
FireFinished();
}
else
{
GetArticleById();
}
}
private void GetArticleById()
{
_articleAvailable = false;
_articleId = ParseTools.UnescapeCaseSensitiveString( _article.GetPropText( NntpPlugin._propArticleId ) );
_getArticleUnit = new AsciiSendLineAndApplyMethodUnit(
"article " + _articleId, ".\r\n", new LineDelegate( getArticleByIdUnitProcessLine ) );
_getArticleUnit.Finished += new AsciiProtocolUnitDelegate( getArticleUnit_Finished );
StartUnit( _getArticleUnit, _connection );
}
private void getArticleByIdUnitProcessLine( string line )
{
if( !_articleAvailable )
{
if( !line.StartsWith( "220" ) )
{
string articleNumber = NewsArticleHelper.GetArticleNumber( _article, _group );
if( articleNumber == null )
{
ProcessErrorResponse( line );
}
else
{
_getArticleUnit.Finished -= new AsciiProtocolUnitDelegate( getArticleUnit_Finished );
FireFinished( _getArticleUnit );
GetArticleByNumber( articleNumber );
}
}
else
{
_lines = new ArrayList();
_articleAvailable = true;
}
}
else
{
ProcessLine( line );
}
}
private void GetArticleByNumber( string articleNumber )
{
_articleAvailable = false;
_getArticleUnit = new AsciiSendLineAndApplyMethodUnit(
"article " + articleNumber, ".\r\n", new LineDelegate( getArticleByNumberUnitProcessLine ) );
_getArticleUnit.Finished += new AsciiProtocolUnitDelegate( getArticleUnit_Finished );
StartUnit( _getArticleUnit, _connection );
}
private void getArticleUnit_Finished( AsciiProtocolUnit unit )
{
if( _articleAvailable )
{
IAsyncProcessor ap = Core.ResourceAP;
if( _group == null )
{
ap.RunUniqueJob( "Creating news article",
new CreateArticleByProtocolHandlerDelegate( NewsArticleParser.CreateArticleByProtocolHandler ),
_lines.ToArray( typeof( string ) ), _article );
}
else
{
ap.QueueJob( _priority, "Creating news article",
new CreateArticleDelegate( NewsArticleParser.CreateArticle ),
_lines.ToArray( typeof( string ) ), _group, _articleId );
}
}
_getArticleUnit = null;
FireFinished();
}
private void getArticleByNumberUnitProcessLine( string line )
{
if( !_articleAvailable )
{
if( !line.StartsWith( "220" ) )
{
ProcessErrorResponse( line );
}
else
{
_lines = new ArrayList();
_articleAvailable = true;
}
}
else
{
ProcessLine( line );
}
}
private const string LINES = "lines: ";
private void ProcessLine( string line )
{
if( _lineCount == 0 )
{
if( string.Compare( line, 0, LINES, 0, LINES.Length, true ) == 0 )
{
try
{
_lineCount = Int32.Parse( line.Substring( 6 ).Trim().TrimEnd( '\r', '\n' ) );
}
catch
{
_lineCount = Int32.MaxValue;
}
_lineIndex = -1;
}
}
else
{
if( _lineIndex == -1 )
{
if( line.TrimEnd( '\r', '\n' ).Length == 0 )
{
_lineIndex = 0;
}
}
else
{
if( OnProgress != null )
{
int percent = _lineIndex * 100 / _lineCount;
if( percent > 100 )
{
percent = 100;
}
OnProgress( this, percent.ToString() );
}
++_lineIndex;
}
}
_lines.Add( line );
}
private void ProcessErrorResponse( string line )
{
/// Never download the article again only in case of the following responses:
/// "430 No such article" and "423 Bad article number".
bool neverDownloadAgain = line.StartsWith( "430" ) || line.StartsWith( "423" );
Core.ResourceAP.QueueJob( _priority,
new SetErrorDelegate( SetError ), line.Remove( 0, 3 ).Trim() + '.', neverDownloadAgain );
FireFinished();
}
private delegate void SetErrorDelegate( string error, bool neverDownloadAgain );
private void SetError( string error, bool neverDownloadAgain )
{
if( !_article.IsDeleted && _article.HasProp( NntpPlugin._propHasNoBody ) )
{
_article.SetProp( Core.Props.LongBody, error );
if( neverDownloadAgain )
{
_article.DeleteProp( NntpPlugin._propHasNoBody );
}
}
}
private IResource _article;
private IResource _group;
private JobPriority _priority;
private bool _setGroup;
private string _articleId;
private bool _articleAvailable;
private int _lineCount;
private int _lineIndex;
private ArrayList _lines;
private AsciiSendLineAndApplyMethodUnit _getArticleUnit;
private AsciiTcpConnection _connection;
}
///
/// headers delivering unit
///
internal class NntpDeliverHeadersFromGroupsUnit : AsciiProtocolUnit
{
public NntpDeliverHeadersFromGroupsUnit( IResourceList groups, IResource groupToIgnore )
{
Interlocked.Increment( ref NntpPlugin._deliverNewsUnitCount );
_groups = groups;
_groupToIgnore = groupToIgnore;
}
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
_groupIndex = 0;
StartNextGroup();
}
protected override void FireFinished()
{
Interlocked.Decrement( ref NntpPlugin._deliverNewsUnitCount );
base.FireFinished();
}
private void StartNextGroup()
{
if( _groupIndex >= _groups.Count )
{
FireFinished();
}
else
{
IResource group;
try
{
group = _groups[ _groupIndex++ ];
}
catch( InvalidResourceIdException )
{
group = _groupToIgnore;
}
if( group != _groupToIgnore && !group.IsDeleted && !group.IsDeleting )
{
StartGroup( group );
}
else
{
StartNextGroup();
}
}
}
private void StartGroup( IResource group )
{
NewsgroupResource groupResource = new NewsgroupResource( group );
NntpDownloadHeadersUnit unit = new NntpDownloadHeadersUnit( groupResource, JobPriority.Normal );
unit.Finished += new AsciiProtocolUnitDelegate( group_Finished );
StartUnit( unit, _connection );
}
private void group_Finished( AsciiProtocolUnit unit )
{
StartNextGroup();
}
private IResourceList _groups;
private IResource _groupToIgnore;
private AsciiTcpConnection _connection;
private int _groupIndex;
}
///
/// headers delivering unit
///
internal class NntpDeliverEmptyArticlesFromGroupsUnit : AsciiProtocolUnit
{
public NntpDeliverEmptyArticlesFromGroupsUnit( IResourceList groups, IResource groupToIgnore )
{
Interlocked.Increment( ref NntpPlugin._deliverNewsUnitCount );
_groups = groups;
_groupToIgnore = groupToIgnore;
}
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
_groupIndex = 0;
Core.ResourceAP.QueueJob( new MethodInvoker( StartNextGroupMarshalled ) );
}
protected override void FireFinished()
{
Core.UIManager.GetStatusWriter( typeof( NntpDeliverEmptyArticlesFromGroupsUnit ),
StatusPane.Network ).ClearStatus();
Interlocked.Decrement( ref NntpPlugin._deliverNewsUnitCount );
base.FireFinished();
}
private void StartNextGroup()
{
if( _groupIndex >= _groups.Count )
{
FireFinished();
}
else
{
IResource group;
try
{
group = _groups[ _groupIndex++ ];
}
catch( InvalidResourceIdException )
{
group = _groupToIgnore;
}
if( group != _groupToIgnore && !group.IsDeleted && !group.IsDeleting )
{
StartGroup( group );
}
else
{
StartNextGroup();
}
}
}
private void StartNextGroupMarshalled()
{
Core.NetworkAP.QueueJob( JobPriority.Immediate, new MethodInvoker( StartNextGroup ) );
}
private void StartGroup( IResource group )
{
_group = new NewsgroupResource( group );
if( !_group.IsSubscribed )
{
StartNextGroup();
}
else
{
Core.UIManager.GetStatusWriter( typeof( NntpDeliverEmptyArticlesFromGroupsUnit ),
StatusPane.Network ).ShowStatus( "Downloading articles from " + _group.DisplayName + "..." );
NntpSetGroupUnit setGroupUnit = new NntpSetGroupUnit( _group );
setGroupUnit.Finished += new AsciiProtocolUnitDelegate( setGroupUnit_Finished );
StartUnit( setGroupUnit, _connection );
}
}
private void setGroupUnit_Finished( AsciiProtocolUnit unit )
{
NntpSetGroupUnit setGroupUnit = (NntpSetGroupUnit) unit;
string response = setGroupUnit.ResponseLine;
if( response == null || !response.StartsWith( "211" ) )
{
StartNextGroup();
}
else
{
_emptyArticles = _group.Resource.GetLinksTo( null, NntpPlugin._propTo );
_emptyArticles = _emptyArticles.Intersect(
Core.ResourceStore.FindResourcesWithProp( null, NntpPlugin._propHasNoBody ), true );
_emptyArticles.Sort( "Date", false );
_articleIndex = 0;
StartNextArticle();
}
}
private void StartNextArticle()
{
if( _articleIndex >= _emptyArticles.Count || _articleIndex >= _group.CountToDownloadAtTime )
{
StartNextGroup();
}
else
{
IResource article;
try
{
article = _emptyArticles[ _articleIndex++ ];
}
catch( InvalidResourceIdException )
{
article = null;
}
if( article == null || article.IsDeleted || article.IsDeleting )
{
StartNextArticle();
}
else
{
NntpDownloadArticleUnit downloadArticleUnit =
new NntpDownloadArticleUnit( article, _group.Resource, JobPriority.Normal, false );
downloadArticleUnit.Finished += new AsciiProtocolUnitDelegate( downloadArticleUnit_Finished );
StartUnit( downloadArticleUnit, _connection );
}
}
}
private void downloadArticleUnit_Finished( AsciiProtocolUnit unit )
{
StartNextArticle();
}
private IResourceList _groups;
private IResource _groupToIgnore;
private int _groupIndex;
private int _articleIndex;
private NewsgroupResource _group;
private IResourceList _emptyArticles;
private AsciiTcpConnection _connection;
}
///
/// posting article unit
///
internal class NntpPostArticleUnit: AsciiProtocolUnit
{
///
/// Creates protocol unit for posting article to server
///
///
///
/// can be null
public NntpPostArticleUnit( IResource draftArticle,
IResource server,
AsciiProtocolUnitDelegate finishedMethod,
bool invokedByUser )
{
_draftArticle = draftArticle;
_server = new ServerResource( server );
_invokedByUser = invokedByUser;
_error = "Failed to connect, posting abandoned.";
if( finishedMethod != null )
{
Finished += finishedMethod;
}
}
public IResource DraftArticle
{
get { return _draftArticle; }
}
///
/// if null then no error
///
public string Error
{
get { return _error; }
}
protected override void Start( AsciiTcpConnection connection )
{
_connection = connection;
Core.UIManager.GetStatusWriter( _server.Name, StatusPane.Network ).ShowStatus(
"Posting article to " + _server.DisplayName );
Core.ResourceAP.QueueJob( _invokedByUser ? JobPriority.Immediate : JobPriority.BelowNormal,
"Preparing articles for posting", new MethodInvoker( PreparePosting ) );
}
protected override void FireFinished()
{
if( _error == null )
{
NewsFolders.PlaceResourceToFolder( _draftArticle, NewsFolders.SentItems );
}
Core.UIManager.GetStatusWriter( _server.Name, StatusPane.Network ).ClearStatus();
base.FireFinished();
}
#region strings for building RFC-822 datetime
private readonly string[] _daysOfTheWeek =
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
private readonly string[] _months =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
#endregion
private void PreparePosting()
{
if( NewsFolders.IsInFolder( _draftArticle, NewsFolders.SentItems ) )
{
Core.NetworkAP.QueueJob(
JobPriority.Immediate, "Finish posting", new MethodInvoker( FireFinished ) );
}
else
{
NewsFolders.PlaceResourceToFolder( _draftArticle, NewsFolders.Outbox );
_nntpBody = _draftArticle.GetPropText( NntpPlugin._propNntpText );
if( !_draftArticle.HasProp( NntpPlugin._propArticleId ) )
{
StringBuilder builder = new StringBuilder( _nntpBody.Length + 64 );
builder.Append( "Date: " );
DateTime postTime = DateTime.UtcNow;
builder.Append( _daysOfTheWeek[ (int)postTime.DayOfWeek ] );
builder.Append( ", " );
builder.Append( postTime.Day );
builder.Append( ' ' );
builder.Append( _months[ postTime.Month - 1 ] );
builder.Append( ' ' );
builder.Append( postTime.Year );
builder.Append( ' ' );
builder.Append( postTime.Hour.ToString().PadLeft( 2, '0' ) );
builder.Append( ':' );
builder.Append( postTime.Minute.ToString().PadLeft( 2, '0' ) );
builder.Append( ':' );
builder.Append( postTime.Second.ToString().PadLeft( 2, '0' ) );
builder.Append( " +0000 (UTC)" );
builder.Append( "\r\nMessage-ID: " );
string message_id = ParseTools.GenerateArticleId( _draftArticle, _server.Name );
builder.Append( message_id );
builder.Append( "\r\n" );
builder.Append( _nntpBody );
_nntpBody = builder.ToString();
_draftArticle.SetProp( NntpPlugin._propArticleId, message_id );
}
AsciiSendLineGetLineUnit initPostUnit = new AsciiSendLineGetLineUnit( "post" );
initPostUnit.Finished += new AsciiProtocolUnitDelegate( initPostUnit_Finished );
Core.NetworkAP.QueueJob( JobPriority.Immediate, "Posting articles",
new StartUnitDelegate( StartUnit ), initPostUnit, _connection );
}
}
private delegate void StartUnitDelegate( AsciiProtocolUnit unit, AsciiTcpConnection connection );
private void initPostUnit_Finished( AsciiProtocolUnit unit )
{
AsciiSendLineGetLineUnit initPostUnit = (AsciiSendLineGetLineUnit) unit;
string response = initPostUnit.ResponseLine;
if( !initPostUnit.LineSent || ( response != null && !response.StartsWith( "340" ) ) )
{
ExtractError( response );
FireFinished();
}
else
{
AsciiSendLineGetLineUnit postUnit = new AsciiSendLineGetLineUnit( _nntpBody );
postUnit.Finished += new AsciiProtocolUnitDelegate( postUnit_Finished );
StartUnit( postUnit, _connection );
}
}
private void postUnit_Finished( AsciiProtocolUnit unit )
{
AsciiSendLineGetLineUnit postUnit = (AsciiSendLineGetLineUnit) unit;
string response = postUnit.ResponseLine;
if( !postUnit.LineSent || response == null || !response.StartsWith( "240" ) )
{
ExtractError( response );
FireFinished();
}
else
{
_error = null;
FireFinished();
}
}
private void ExtractError( string response )
{
if( response != null )
{
_error = ( response.Length > 3 ) ? response.Substring( 3 ).Trim() : "Timeout occurred";
}
else
{
Exception exception = _connection.LastSocketException;
_error = ( exception != null ) ? exception.Message : "Indeterminate error";
}
}
private IResource _draftArticle;
private ServerResource _server;
private AsciiTcpConnection _connection;
private string _nntpBody;
private string _error;
private bool _invokedByUser;
}
}