using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using ThreadHelper;
using IssueVision.localhost;
namespace IssueVision
{
public enum SyncThread
{
MainThread = 0,
BackgroundThread = 1
}
public class IssueSubject : Component, ISubject
{
#region Delagate and Event Declarations
public delegate void PositionChangedEventHandler(object sender, EventArgs e);
public delegate void IssueDataChangedEventHandler(object sender, EventArgs e);
public delegate void ConflictDataChangedEventHandler(object sender, EventArgs e);
public delegate void LookupDataChangedEventHandler(object sender, EventArgs e);
public delegate void Synchronization_StartedEventHandler(object sender, EventArgs e);
public delegate void Synchronization_FinishedEventHandler(object sender, EventArgs e);
public delegate void DataChanged(IVDataSet newIssues);
// PositionChanged is raised when a different issue is selected from the list
public virtual event PositionChangedEventHandler PositionChanged;
// IssueDataChanged is raised when the data changes, either because the user
// edited the data, or because fresh data has arrived from the server
public virtual event IssueDataChangedEventHandler IssueDataChanged;
// ConflictDataChanged changes when a conflict is resolved, or new conflicts are
// detected.
public virtual event ConflictDataChangedEventHandler ConflictDataChanged;
// LookupDataChanged is raised when lookup data is downloaded from the server
public virtual event LookupDataChangedEventHandler LookupDataChanged;
// Synchronization_Started is raised when a background synchronization starts.
public virtual event Synchronization_StartedEventHandler Synchronization_Started;
// Synchronization_Started is raised when a background synchronization finishes.
public virtual event Synchronization_FinishedEventHandler Synchronization_Finished;
#endregion
#region Internal Members
private IVDataSet m_dataSet = null;
private Timer tmrBackgroundThread;
private BackgroundWorker BackgroundWorkerThread;
private bool m_isBackgroundThreadRunning = false;
private Control m_parent = null;
private bool m_showClosedIssues = true;
private DataView m_currentView = null;
private DataRowView m_currentItem = null;
private Hashtable m_conflictItems = null;
private string m_filterTable = string.Empty;
private string m_filterMember = string.Empty;
private string m_filterValue = string.Empty;
private int m_loggedInStafferID = 0;
private string m_loggedInDisplayName = string.Empty;
private bool m_isOnline = true;
private static DateTime m_lastModified = new DateTime(1900, 1, 1);
#endregion
#region Public Properties
public IVDataSet.StaffersDataTable Staffers
{
get
{
return m_dataSet.Staffers;
}
}
public DataRowView CurrentItem
{
get
{
return m_currentItem;
}
set
{
m_currentItem = value;
if (PositionChanged != null)
{
PositionChanged(this, EventArgs.Empty);
}
}
}
public DataView CurrentView
{
get
{
return m_currentView;
}
set
{
m_currentView = value;
}
}
public Hashtable ConflictItems
{
get
{
return m_conflictItems;
}
set
{
m_conflictItems = value;
}
}
public IVDataSet.ConflictsDataTable Conflicts
{
get
{
return m_dataSet.Conflicts;
}
}
public bool HasConflicts
{
get
{
return Conflicts.Rows.Count > 0;
}
}
public bool IsOnline
{
set
{
m_isOnline = value;
tmrBackgroundThread.Enabled = m_isOnline;
}
}
public IVDataSet.IssuesDataTable Issues
{
get
{
return m_dataSet.Issues;
}
}
public IVDataSet.IssueHistoryDataTable IssueHistory
{
get
{
return m_dataSet.IssueHistory;
}
}
public IVDataSet.IssueTypesDataTable IssueTypes
{
get
{
return m_dataSet.IssueTypes;
}
}
public Control Parent
{
get
{
return m_parent;
}
set
{
m_parent = value;
}
}
public bool ShowClosedIssues
{
get
{
return m_showClosedIssues;
}
set
{
m_showClosedIssues = value;
PrepareCurrentView();
}
}
#endregion
#region Component Designer generated code
private IContainer components = null;
public IssueSubject(IContainer Container) : this()
{
Container.Add(this);
}
public IssueSubject()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
[DebuggerStepThroughAttribute()]
private void InitializeComponent()
{
components = new Container();
tmrBackgroundThread = new Timer(components);
BackgroundWorkerThread = new BackgroundWorker();
BackgroundWorkerThread.WorkerReportsProgress = true;
BackgroundWorkerThread.WorkerSupportsCancellation = true;
tmrBackgroundThread.Tick += new EventHandler(this.tmrBackgroundThread_Tick);
BackgroundWorkerThread.DoWork += new ThreadHelper.DoWorkEventHandler(this.OnDoWork);
BackgroundWorkerThread.RunWorkerCompleted += new ThreadHelper.RunWorkerCompletedEventHandler(this.OnCompleted);
}
#endregion
#region IssueSubject Initialization
// Initialize the application's data and state
public void Initialize()
{
// load data
LoadIssueData();
UpdateLastModifiedDate();
if (!UserSettings.Instance.WorkOffline)
{
SendReceiveIssueData(SyncThread.MainThread);
}
m_showClosedIssues = true;
RenderIssuesToUI();
// set current user
DataRow[] rows = Staffers.Select("UserName = \'" + UserSettings.Instance.Username + "\'");
m_loggedInStafferID = (int)rows[0]["StafferID"];
m_loggedInDisplayName = (string)rows[0]["DisplayName"];
tmrBackgroundThread.Interval = int.Parse(ConfigurationSettings.AppSettings["SyncInterval"]);
}
#endregion
#region Background Update Code
// tmrBackgroundThread.Tick: background thread is started on timer tick
private void tmrBackgroundThread_Tick(object sender, EventArgs e)
{
if (!m_isBackgroundThreadRunning)
{
SendReceiveIssueData(SyncThread.BackgroundThread);
}
}
// entry point for Send/Receive data
public void SendReceiveIssueData(SyncThread behavior)
{
if (Synchronization_Started != null)
{
Synchronization_Started(this, EventArgs.Empty);
}
if (behavior == SyncThread.BackgroundThread)
{
m_isBackgroundThreadRunning = true;
BackgroundWorkerThread.RunWorkerAsync(m_lastModified);
}
else
{
IVDataSet newIssues = GetNewIssues(m_lastModified);
MergeNewIssues(newIssues);
}
}
// BackgroundWorkerThread.DoWork: this method is run on the background thread to do its work
private void OnDoWork(object sender, DoWorkEventArgs doWorkArgs)
{
DateTime lastAccessed = (DateTime)doWorkArgs.Argument;
doWorkArgs.Result = GetNewIssues(lastAccessed);
}
// sends and received data to/from the web service
private IVDataSet GetNewIssues(DateTime lastAccessed)
{
IVDataSet newIssues = null;
try
{
DataSet changesDataSet = m_dataSet.GetChanges(DataRowState.Added | DataRowState.Modified);
newIssues = WebServicesLayer.SendReceiveIssues(changesDataSet, lastAccessed);
}
catch
{
// Eat this exception. It is logged on the service side, and the caller of this
// method tests if newIssues is null to decide whether the service is currently
// available.
}
return newIssues;
}
// BackgroundWorkerThread.RunWorkerCompleted: This method runs on the timer thread
// when the background thread finishes it's work
private void OnCompleted(object sender, RunWorkerCompletedEventArgs completedArgs)
{
// newIssues is return from the background thread to the timer thread
IVDataSet newIssues = (IVDataSet)completedArgs.Result;
// Parent.Invoke passes the newIssues data to MergeNewIssues on the MainForm thread
m_parent.Invoke(new DataChanged(this.MergeNewIssues), new object[]{newIssues});
}
#endregion
#region Data Merging/Display Methods
// Merge the issue data
private void MergeNewIssues(IVDataSet newIssues)
{
// Process new data, if any
if (newIssues != null && newIssues.HasChanges(DataRowState.Unchanged))
{
m_dataSet.Merge(newIssues, false);
m_dataSet.AcceptChanges();
UpdateLastModifiedDate();
RenderIssuesToUI();
}
m_isBackgroundThreadRunning = false;
if (Synchronization_Finished != null)
{
Synchronization_Finished(this, EventArgs.Empty);
}
}
private void RenderIssuesToUI()
{
PrepareLookupTables();
PrepareConflicts();
PrepareCurrentView();
}
public void SetFilter(string filterTable, string filterMember, string filterValue)
{
m_filterTable = filterTable;
m_filterMember = filterMember;
m_filterValue = filterValue;
PrepareCurrentView();
}
// Filters current view to hide/show closed issues
private void PrepareCurrentView()
{
string filterString = string.Empty;
if (m_filterValue != string.Empty)
{
filterString = m_filterMember + " = " + m_filterValue;
}
if (!ShowClosedIssues)
{
if (filterString == string.Empty)
{
filterString = "IsOpen = 1";
}
else
{
filterString += " AND IsOpen = 1";
}
}
if (m_filterTable == "Issues" || m_filterTable == string.Empty)
m_currentView = new DataView(Issues);
else
m_currentView = new DataView(Conflicts);
m_currentView.RowFilter = filterString;
m_currentView.Sort = "IssueTypeID";
if (IssueDataChanged != null)
{
IssueDataChanged(this, EventArgs.Empty);
}
}
private void PrepareConflicts()
{
if (Conflicts != null && Conflicts.Rows.Count > 0)
{
ConflictItems = new Hashtable(Conflicts.Rows.Count + 1);
foreach (IVDataSet.ConflictsRow clientRow in Conflicts.Rows)
{
clientRow.HasConflicts = true;
IVDataSet.IssuesRow serverRow = (IVDataSet.IssuesRow)Issues.Rows.Find(clientRow.IssueID);
ConflictItems.Add(clientRow.IssueID, new ConflictItem(clientRow, serverRow));
}
if (ConflictDataChanged != null)
{
ConflictDataChanged(this, EventArgs.Empty);
}
}
}
private void PrepareLookupTables()
{
if (m_dataSet.Staffers.Count == 0 && m_dataSet.IssueTypes.Count == 0)
{
IVDataSet lookupTables = WebServicesLayer.GetLookupTables();
m_dataSet.Merge(lookupTables, true);
if (LookupDataChanged != null)
{
LookupDataChanged(this, EventArgs.Empty);
}
}
}
private void UpdateLastModifiedDate()
{
DataView dv = new DataView(Issues);
dv.Sort = "DateModified DESC";
if (dv.Count > 0)
m_lastModified = (DateTime)dv[0]["DateModified"];
}
#endregion
#region Issue Adding, Updating and Resolving
// Add new issue
public void AddIssue(int stafferID, int issueTypeID, string title, string description)
{
DataRow rowStaffer = m_dataSet.Staffers.Select("StafferId=" + stafferID.ToString())[0];
DataRow rowIssueType = m_dataSet.IssueTypes.Select("IssueTypeID=" + issueTypeID.ToString())[0];
IVDataSet.IssuesRow row = m_dataSet.Issues.AddIssuesRow(stafferID,
issueTypeID,
title,
description,
DateTime.Now,
DateTime.MinValue,
true,
DateTime.Now,
(string)rowStaffer["UserName"],
(string)rowStaffer["DisplayName"],
(string)rowIssueType["IssueType"],
false,
true);
if (IssueDataChanged != null)
{
IssueDataChanged(this, EventArgs.Empty);
}
}
public void UpdateIssue(int issueID, int stafferID, string stafferName, string comment, bool isOpen)
{
// Find this issue in the current dataset
DataRow dr = m_dataSet.Issues.Rows.Find(issueID);
dr.BeginEdit();
if (isOpen)
{
dr["IsOpen"] = 1;
}
else
{
dr["IsOpen"] = 0;
}
dr["StafferID"] = stafferID;
dr["DisplayName"] = stafferName;
dr.EndEdit();
if (comment.Trim() != string.Empty)
{
m_dataSet.IssueHistory.AddIssueHistoryRow(m_loggedInStafferID, issueID, comment, DateTime.Now, m_loggedInDisplayName);
if (IssueDataChanged != null)
{
IssueDataChanged(this, EventArgs.Empty);
}
}
}
public void ResolveConflict(ConflictItem conflict, bool keepClientVersion)
{
if (keepClientVersion)
{
conflict.ServerVersion["StafferID"] = conflict.ClientVersion["StafferID"];
conflict.ServerVersion["DisplayName"] = conflict.ClientVersion["DisplayName"];
conflict.ServerVersion["IsOpen"] = conflict.ClientVersion["IsOpen"];
}
Conflicts.Rows.Remove(conflict.ClientVersion);
ConflictItems.Remove(conflict.ServerVersion["IssueID"]);
((IVDataSet.IssuesRow)conflict.ServerVersion).HasConflicts = false;
if (ConflictDataChanged != null)
{
ConflictDataChanged(this, EventArgs.Empty);
}
if (IssueDataChanged != null)
{
IssueDataChanged(this, EventArgs.Empty);
}
}
#endregion
#region DataSet Serialization Methods
public void SaveIssueData()
{
string filePath = Application.UserAppDataPath + "\\" + ConfigurationSettings.AppSettings["DataSetFileName"];
SerializationHelper.Serialize(m_dataSet, filePath);
}
private void LoadIssueData()
{
m_dataSet = GetOfflineData();
if (m_dataSet == null)
{
m_dataSet = new IVDataSet();
}
m_dataSet.DataSetName = "IssueSubject";
if (LookupDataChanged != null)
{
LookupDataChanged(this, EventArgs.Empty);
}
}
public static IVDataSet GetOfflineData()
{
IVDataSet ivDataSet = null;
try
{
string filePath = Application.UserAppDataPath + "\\" + ConfigurationSettings.AppSettings["DataSetFileName"];
ivDataSet = (IVDataSet)SerializationHelper.Deserialize(filePath);
}
catch
{
// Eat this exception. The caller of this method tests if ivDataSet is null to decide whether
// offline data is available.
}
return ivDataSet;
}
#endregion
#region Reporting methods
public int GetClosedCount()
{
char[] delimiter = " ".ToCharArray();
string[] filterParts = m_currentView.RowFilter.Split(delimiter);
DataView dv = new DataView(Issues);
if (filterParts[0] == "StafferID")
{
dv.RowFilter = "StafferID = " + filterParts[2] + " AND IsOpen = 0";
}
else
{
dv.RowFilter = "IsOpen = 0";
}
return dv.Count;
}
public int GetEscalatedCount()
{
int open = GetOpenCount();
return (int)Math.Round(open * 0.3);
}
public int GetOpenCount()
{
char[] delimiter = " ".ToCharArray();
string[] filterParts = m_currentView.RowFilter.Split(delimiter);
DataView dv = new DataView(Issues);
if (filterParts[0] == "StafferID")
{
dv.RowFilter = "StafferID = " + filterParts[2] + " AND IsOpen = 1";
}
else
{
dv.RowFilter = "IsOpen = 1";
}
return dv.Count;
}
#endregion
}
}