2020-08-31 14:02:41 +00:00
/ *
buypeeb - a program to track yahoo jp auctions of peebus .
Copyright ( C ) 2020 lynnesbian
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < https : //www.gnu.org/licenses/>.
* /
2020-08-28 16:04:37 +00:00
using System ;
2020-09-01 10:41:29 +00:00
using System.Collections.Generic ;
2021-06-15 00:20:46 +00:00
using System.Diagnostics ;
using System.Diagnostics.CodeAnalysis ;
using System.Globalization ;
2020-09-03 13:47:36 +00:00
using System.IO ;
2021-06-15 00:20:46 +00:00
using System.Linq ;
using System.Net ;
using System.Text.Encodings.Web ;
2020-09-03 13:47:36 +00:00
using System.Text.Json ;
2021-06-15 00:20:46 +00:00
using System.Text.RegularExpressions ;
using System.Text.Unicode ;
2020-09-03 05:23:23 +00:00
using System.Threading ;
using System.Threading.Tasks ;
2020-09-05 05:02:37 +00:00
using CsvHelper ;
2020-08-28 16:04:37 +00:00
using Gtk ;
2021-06-15 00:20:46 +00:00
using Timeout = GLib . Timeout ;
// ReSharper disable UnusedParameter.Local
2020-08-28 16:04:37 +00:00
namespace Buypeeb {
2021-06-15 00:20:46 +00:00
[SuppressMessage("ReSharper", "UnusedMember.Local")]
internal class MainWindow : Window {
2020-09-03 13:47:36 +00:00
private string location ;
2020-09-05 05:02:37 +00:00
private JsonSerializerOptions jsonOptions ;
2020-09-03 13:47:36 +00:00
2020-09-02 03:21:32 +00:00
private ListStore items ;
private Settings settings ;
2020-09-03 06:02:56 +00:00
private TreeView itemTreeView ;
2020-09-04 07:23:32 +00:00
private Builder builder ;
// TODO: whenever we get something from the builder, cache it for later
// that way we don't need to constantly do "builder.GetObject"s
2020-09-04 10:48:47 +00:00
// when that is done, you can use the cache array to replace everything from here...
2021-06-15 00:34:11 +00:00
private readonly Box selectionViewBox ;
private readonly Label endingLabel ;
2020-09-05 03:45:18 +00:00
private bool queueActive ;
2021-06-15 00:34:11 +00:00
private readonly SearchEntry searchEntry ;
private readonly Dictionary < string , CheckButton > filterChecks = new Dictionary < string , CheckButton > ( ) ;
2020-09-04 10:48:47 +00:00
// ...to here.
2020-08-31 14:20:56 +00:00
2021-06-15 00:20:46 +00:00
private static SemaphoreSlim taskLimit = new SemaphoreSlim ( 6 ) ;
2021-06-15 00:34:11 +00:00
private readonly Queue < string > updateQueue = new Queue < string > ( ) ;
private IEnumerable < YahooAuctionsItem > filterQuery = >
// father forgive me for i have lynned
from item in settings . watchlist . Values . ToList ( )
where ( item . favourite ! = filterChecks [ "Favourites" ] . Active | |
item . favourite = = filterChecks [ "NonFavourites" ] . Active ) & &
( item . Available ! = filterChecks [ "Active" ] . Active | |
item . Available = = filterChecks [ "Ended" ] . Active ) & &
( item . endingToday ! = filterChecks [ "EndingToday" ] . Active | |
item . endingToday = = filterChecks [ "EndingAfterToday" ] . Active ) & &
( item . hasWinPrice ! = filterChecks [ "WithWinPrice" ] . Active | |
item . hasWinPrice = = filterChecks [ "WithNoWinPrice" ] . Active ) & &
( string . IsNullOrWhiteSpace ( searchEntry . Text ) | |
item . name . ToLower ( ) . Contains ( searchEntry . Text . ToLower ( ) ) | |
item . originalName . ToLower ( ) . Contains ( searchEntry . Text . ToLower ( ) ) )
select item ;
private IEnumerable < YahooAuctionsItem > outdatedItemQuery = >
// only returns items that meet all of the following:
// - marked as "ready", as in, they aren't in the process of updating
// - not updated since the interval
from item in settings . watchlist . Values . ToList ( )
where item . Ready & & settings . ItemNotUpdatedSinceInterval ( item )
select item ;
2020-09-03 05:23:23 +00:00
2020-09-04 09:17:59 +00:00
private YahooAuctionsItem selectedItem {
2020-09-04 07:23:32 +00:00
get {
2021-06-15 00:20:46 +00:00
if ( itemTreeView . Selection . CountSelectedRows ( ) = = 0 ) {
2020-09-04 09:17:59 +00:00
// avoids incurring the wrath of Gtk-CRITICAL **
return null ;
}
2021-06-15 00:20:46 +00:00
itemTreeView . Selection . GetSelected ( out var iter ) ;
2021-06-15 00:34:11 +00:00
return ( YahooAuctionsItem ) itemTreeView . Model . GetValue ( iter , 0 ) ;
2020-09-04 07:23:32 +00:00
}
}
2021-06-15 00:34:11 +00:00
public MainWindow ( ) : this ( new Builder ( "main.glade" ) ) {
}
2020-08-28 16:04:37 +00:00
private MainWindow ( Builder builder ) : base ( builder . GetObject ( "wndMain" ) . Handle ) {
2021-06-15 00:20:46 +00:00
jsonOptions = new JsonSerializerOptions {
Encoder = JavaScriptEncoder . Create ( UnicodeRanges . All ) ,
2020-09-05 05:02:37 +00:00
} ;
2020-09-03 13:47:36 +00:00
if ( Environment . OSVersion . Platform = = PlatformID . Win32NT ) {
// C:\Users\Beebus\AppData\Roaming\Lynnear Software\buypeeb
2021-06-15 00:34:11 +00:00
location = System . IO . Path . Combine ( Environment . ExpandEnvironmentVariables ( "%APPDATA%" ) , "Lynnear Software" ,
"buypeeb" ) ;
} else {
2020-09-03 13:47:36 +00:00
// ~/.config/Lynnear Software/buypeeb
2021-06-15 00:34:11 +00:00
location = System . IO . Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) , ".config" ,
"Lynnear Software" , "buypeeb" ) ;
2020-09-03 13:47:36 +00:00
}
2021-06-15 00:20:46 +00:00
var userdata = System . IO . Path . Combine ( location , "userdata.json" ) ;
2020-09-03 13:47:36 +00:00
if ( File . Exists ( userdata ) ) {
try {
2021-06-15 00:20:46 +00:00
var j = File . ReadAllText ( userdata ) ;
settings = JsonSerializer . Deserialize < Settings > ( j ) ;
2021-06-15 00:34:11 +00:00
} catch {
2020-09-03 13:47:36 +00:00
// ???
Console . WriteLine ( "oops" ) ;
Application . Quit ( ) ;
}
2021-06-15 00:34:11 +00:00
} else {
2021-06-15 00:20:46 +00:00
settings = new Settings ( ) ;
2020-09-03 13:47:36 +00:00
}
2021-06-15 00:34:11 +00:00
2021-06-15 00:20:46 +00:00
SaveSettings ( ) ;
Title = "Buypeeb" ;
2020-09-03 05:23:23 +00:00
2020-09-04 07:23:32 +00:00
this . builder = builder ;
2020-08-28 16:04:37 +00:00
builder . Autoconnect ( this ) ;
2020-09-03 06:02:56 +00:00
2021-06-15 00:34:11 +00:00
var menuButtonFilter = ( MenuButton ) builder . GetObject ( "MenuButtonFilter" ) ;
var menuButtonSort = ( MenuButton ) builder . GetObject ( "MenuButtonSort" ) ;
menuButtonFilter . Child = ( Image ) builder . GetObject ( "ImageFilter" ) ;
menuButtonSort . Child = ( Image ) builder . GetObject ( "ImageSort" ) ;
selectionViewBox = ( Box ) builder . GetObject ( "SelectionViewBox" ) ;
endingLabel = ( Label ) builder . GetObject ( "LabelSelectedEnding" ) ;
searchEntry = ( SearchEntry ) builder . GetObject ( "FilterSearchEntry" ) ;
foreach ( var name in new List < string > {
"Favourites" , "NonFavourites" , "Active" , "Ended" , "EndingToday" , "EndingAfterToday" , "WithWinPrice" ,
"WithNoWinPrice"
} ) {
filterChecks . Add ( name , ( CheckButton ) builder . GetObject ( $"CheckButtonFilter{name}" ) ) ;
2021-06-15 00:20:46 +00:00
filterChecks [ name ] . Active = false ;
2020-09-05 06:51:28 +00:00
}
2020-09-03 06:02:56 +00:00
// bind treeview columns to watchlist instead of needing to manually sync its liststore
2021-06-15 00:34:11 +00:00
itemTreeView = ( TreeView ) builder . GetObject ( "TreeViewItems" ) ;
2021-06-15 00:20:46 +00:00
items = new ListStore ( typeof ( YahooAuctionsItem ) ) ;
var filteredItems = new TreeModelFilter ( items , null ) { VisibleFunc = ItemFilter } ;
2020-09-03 05:23:23 +00:00
2021-06-15 00:20:46 +00:00
itemTreeView . Model = filteredItems ;
2020-09-03 16:08:31 +00:00
TreeCellDataFunc [ ] funcs = {
2021-06-15 00:20:46 +00:00
RenderColumnFavourite ,
RenderColumnName ,
RenderColumnPriceYen ,
RenderColumnPriceAUD ,
RenderColumnEnding ,
2020-09-03 16:08:31 +00:00
} ;
2021-06-15 00:20:46 +00:00
for ( var i = 0 ; i < itemTreeView . Columns . Length ; i + + ) {
var c = itemTreeView . Columns [ i ] ;
2020-09-03 16:08:31 +00:00
c . SetCellDataFunc ( c . Cells [ 0 ] , funcs [ i ] ) ;
2020-09-03 06:34:42 +00:00
}
2021-06-15 00:20:46 +00:00
RenderList ( ) ;
UpdateItems ( ) ;
Timeout . Add ( 1000 , UpdateSelectionEndTime ) ;
Timeout . Add ( 10000 , AutoUpdateItems ) ;
2020-09-03 06:02:56 +00:00
2020-09-04 09:17:59 +00:00
DeleteEvent + = WindowShutdown ;
2020-08-28 16:04:37 +00:00
}
2020-09-04 09:17:59 +00:00
private void WindowShutdown ( object sender , DeleteEventArgs args ) {
2020-09-03 14:38:41 +00:00
SaveSettings ( ) ;
2020-08-28 16:04:37 +00:00
Application . Quit ( ) ;
}
2020-09-01 10:41:29 +00:00
// general behaviour
2020-08-28 16:58:14 +00:00
2020-09-06 10:35:37 +00:00
/// <summary>
/// gets the path and iter for a given item id.
/// </summary>
/// <param name="id">the item id to find in the treeview</param>
/// <returns>a tuple of (TreePath, TreeIter)</returns>
2020-09-03 16:09:19 +00:00
private ( TreePath path , TreeIter iter ) GetRow ( string id ) {
// TODO: surely there's a better way to do this
TreeIter iter ;
2021-06-15 00:20:46 +00:00
itemTreeView . Model . GetIterFirst ( out iter ) ;
2021-06-15 00:34:11 +00:00
var m = ( TreeModelFilter ) itemTreeView . Model ;
2020-09-03 16:09:19 +00:00
2021-06-15 00:20:46 +00:00
for ( var i = 0 ; i < itemTreeView . Model . IterNChildren ( ) ; i + + ) {
2021-06-15 00:34:11 +00:00
var x = ( YahooAuctionsItem ) itemTreeView . Model . GetValue ( iter , 0 ) ;
2020-09-03 16:09:19 +00:00
if ( x . id = = id ) {
2020-09-05 06:51:28 +00:00
return ( m . ConvertPathToChildPath ( m . GetPath ( iter ) ) , m . ConvertIterToChildIter ( iter ) ) ;
2020-09-03 16:09:19 +00:00
}
2021-06-15 00:20:46 +00:00
itemTreeView . Model . IterNext ( ref iter ) ;
2020-09-03 16:09:19 +00:00
}
Console . WriteLine ( $"Couldn't find {id}!" ) ;
return ( null , iter ) ;
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// saves the settings to userdata.json.
/// </summary>
2020-09-03 13:47:36 +00:00
private void SaveSettings ( ) {
2021-06-15 00:20:46 +00:00
var j = JsonSerializer . Serialize ( settings , jsonOptions ) ;
var p = System . IO . Path . Combine ( location , "userdata.json" ) ;
2020-09-03 13:47:36 +00:00
Console . WriteLine ( j ) ;
2021-06-15 00:20:46 +00:00
if ( ! Directory . Exists ( location ) ) {
Directory . CreateDirectory ( location ) ;
2020-09-03 14:19:13 +00:00
}
2020-09-05 18:51:47 +00:00
2021-06-15 00:20:46 +00:00
using ( var fs = File . CreateText ( p ) ) {
2020-09-05 18:51:47 +00:00
fs . Write ( j ) ;
2020-09-03 14:17:28 +00:00
}
2020-09-03 13:47:36 +00:00
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// updates the item with the given id. this method blocks and is intended to be run from a task.
/// </summary>
/// <param name="id">the id of the item to update</param>
2020-09-03 05:23:23 +00:00
private void UpdateThread ( string id ) {
2021-06-15 00:20:46 +00:00
var item = settings . watchlist [ id ] ;
2020-09-05 03:45:18 +00:00
// Console.WriteLine($"Updating {id}...");
2020-09-03 13:47:36 +00:00
// set item.ready to false to show that it's still being updated
// this changes a few behaviours, such as displaying the price as "..." instead of whatever's currently stored
2021-06-15 00:20:46 +00:00
item . Ready = false ;
2020-09-03 16:09:19 +00:00
2021-06-15 00:20:46 +00:00
Application . Invoke ( delegate {
2020-09-03 16:09:19 +00:00
// TODO: find a way to not have to do this. i think we need to avoid actually modifying the items outside of the main thread :/
2021-06-15 00:20:46 +00:00
var pathAndIter = GetRow ( id ) ;
2020-09-05 06:51:28 +00:00
if ( pathAndIter . path ! = null ) {
2021-06-15 00:20:46 +00:00
items . EmitRowChanged ( pathAndIter . path , pathAndIter . iter ) ;
2020-09-05 06:51:28 +00:00
}
2020-09-03 05:23:23 +00:00
} ) ;
2020-09-03 16:09:19 +00:00
2021-06-15 00:20:46 +00:00
using ( var client = new WebClient ( ) ) {
2020-09-03 16:09:19 +00:00
// TODO: download should have timeout
2020-09-05 06:52:09 +00:00
item . Update ( client . DownloadString ( item . url ) ) ;
// Thread.Sleep(5000);
// item.Update(File.ReadAllText("yahoo.html"));
2020-09-03 16:09:19 +00:00
}
2021-06-15 00:20:46 +00:00
Application . Invoke ( delegate {
var pathAndIter = GetRow ( id ) ;
2020-09-05 06:51:28 +00:00
if ( pathAndIter . path ! = null ) {
2021-06-15 00:20:46 +00:00
items . EmitRowChanged ( pathAndIter . path , pathAndIter . iter ) ;
2020-09-05 06:51:28 +00:00
}
2021-06-15 00:34:11 +00:00
2021-06-15 00:20:46 +00:00
if ( item = = selectedItem ) {
2020-09-05 07:37:41 +00:00
// if the user has this item selected and it just became ready, enable the selection box and redraw the info
2021-06-15 00:20:46 +00:00
selectionViewBox . Sensitive = true ;
UpdateSelectionView ( ) ;
2020-09-04 07:23:32 +00:00
}
2020-09-03 16:09:19 +00:00
} ) ;
2021-06-15 00:20:46 +00:00
item . Ready = true ;
2020-09-05 03:45:18 +00:00
// Console.WriteLine($"{id} updated.");
2020-09-03 05:23:23 +00:00
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// recursively processes the update queue. this is a blocking function.
/// </summary>
2020-09-05 03:45:18 +00:00
private void ProcessUpdateQueue ( ) {
2021-06-15 00:20:46 +00:00
queueActive = true ;
UpdateItem ( updateQueue . Dequeue ( ) ) ;
if ( updateQueue . TryPeek ( out var _ ) ) {
ProcessUpdateQueue ( ) ;
2021-06-15 00:34:11 +00:00
} else {
2021-06-15 00:20:46 +00:00
queueActive = false ;
2020-09-05 03:45:18 +00:00
}
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// updates an item with the given id with a new task.
/// </summary>
/// <param name="id">the id of the task to update</param>
/// <param name="renderListWhenDone">whether or not to call this.RenderList() after updating the item</param>
2020-09-06 05:30:34 +00:00
private void UpdateItem ( string id , bool renderListWhenDone = false ) {
2021-06-15 00:20:46 +00:00
var item = settings . watchlist [ id ] ;
2020-09-06 05:27:11 +00:00
if ( item . updatedRecently ) {
2020-09-04 10:16:42 +00:00
// the item has been updated recently, and force is not true
return ;
}
2020-09-05 03:45:18 +00:00
// don't start a new task if there are more than `tasklimit` tasks currently running
// this makes sure we don't make 1000 simultaneous requests to yahoo auctions if there are 1000 items on the watchlist
2020-09-04 09:17:59 +00:00
taskLimit . Wait ( ) ;
2021-06-15 00:34:11 +00:00
Task . Factory . StartNew ( ( ) = > { UpdateThread ( id ) ; } ) . ContinueWith ( task = > {
2020-09-06 05:27:11 +00:00
taskLimit . Release ( ) ;
2020-09-06 05:30:34 +00:00
if ( renderListWhenDone ) {
2021-06-15 00:34:11 +00:00
Application . Invoke ( delegate { RenderList ( ) ; } ) ;
2020-09-06 05:27:11 +00:00
}
} ) ;
2020-09-03 05:23:23 +00:00
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// add every item in the watchlist to the update queue. if the update queue is already being processed (this.queueActive), this will do nothing.
/// </summary>
2020-09-03 05:23:23 +00:00
private void UpdateItems ( ) {
2021-06-15 00:20:46 +00:00
if ( queueActive ) {
2020-09-05 03:45:18 +00:00
return ;
}
2021-06-15 00:20:46 +00:00
selectionViewBox . Sensitive = false ;
foreach ( var item in settings . watchlist ) {
if ( ! updateQueue . Contains ( item . Key ) ) {
2020-09-05 03:45:18 +00:00
// set everything to not ready first
// ensures other actions don't attempt to display the items even if UpdateItem hasn't started yet
2021-06-15 00:20:46 +00:00
item . Value . Ready = false ;
updateQueue . Enqueue ( item . Key ) ;
2020-09-05 03:45:18 +00:00
}
}
2020-09-04 07:23:32 +00:00
2021-06-15 00:20:46 +00:00
if ( ! updateQueue . TryPeek ( out var _ ) ) {
2020-09-05 03:45:18 +00:00
// queue is empty
return ;
}
2021-06-15 00:20:46 +00:00
itemTreeView . QueueDraw ( ) ;
2021-06-15 00:34:11 +00:00
Task . Factory . StartNew ( ( ) = > { ProcessUpdateQueue ( ) ; } ) ;
2020-09-04 07:23:32 +00:00
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// updates the selection view, displaying the id, name, etc. for the currently selected item.
/// </summary>
2020-09-04 07:23:32 +00:00
private void UpdateSelectionView ( ) {
// get the currently selected item
2021-06-15 00:20:46 +00:00
var item = selectedItem ;
2021-06-15 00:34:11 +00:00
var infobox = ( Box ) builder . GetObject ( "SelectionInfoBox" ) ;
2020-09-04 07:23:32 +00:00
if ( item = = null ) {
2021-06-15 00:20:46 +00:00
selectionViewBox . Sensitive = false ;
2020-09-04 07:23:32 +00:00
infobox . Visible = false ;
2021-06-15 00:20:46 +00:00
( ( Label ) builder . GetObject ( "LabelSelectedName" ) ) . Text = "buypeeb" ;
2020-09-04 07:23:32 +00:00
return ;
}
2021-06-15 00:20:46 +00:00
selectionViewBox . Sensitive = item . Ready ;
2020-09-04 07:23:32 +00:00
infobox . Visible = true ;
2020-09-05 02:45:23 +00:00
var info = new Dictionary < string , string > {
2021-06-15 00:34:11 +00:00
{ "Name" , item . name } ,
{ "YahooName" , item . originalName } ,
{ "Price" , item . priceJpy } ,
{ "PriceAUD" , item . priceAud } ,
{ "Ending" , "Please wait..." } ,
{ "Bids" , $"{item.Bids}" } ,
{ "BuyItNow" , item . WinPrice = = 0 ? "No" : $"{item.winPriceJpy} ({item.winPriceAud})" } ,
{ "AutoExtension" , item . AutoExtension ? "Yes" : "No" } ,
{ "LastUpdated" , $"Last updated: {(item.Ready ? item.LastUpdated.ToString(" MMM dd , HH : mm : ss ") : " Right now ! ")}" } ,
2020-09-05 02:45:23 +00:00
} ;
foreach ( var row in info ) {
2021-06-15 00:20:46 +00:00
( ( Label ) builder . GetObject ( $"LabelSelected{row.Key}" ) ) . Text = row . Value ;
2020-09-04 07:23:32 +00:00
}
2021-06-15 00:34:11 +00:00
2021-06-15 00:20:46 +00:00
UpdateSelectionEndTime ( ) ;
2020-09-04 07:23:32 +00:00
2021-06-15 00:34:11 +00:00
var noteBuffer = ( TextBuffer ) builder . GetObject ( "TextBufferSelectedNotes" ) ;
2020-09-06 03:33:03 +00:00
noteBuffer . Clear ( ) ;
2021-06-15 00:34:11 +00:00
if ( ! string . IsNullOrWhiteSpace ( item . notes ) ) {
2020-09-06 03:33:03 +00:00
noteBuffer . Text = item . notes ;
}
2021-06-15 00:20:46 +00:00
( ( ToggleButton ) builder . GetObject ( "ButtonSelectedFavourite" ) ) . Active = item . favourite ;
2020-09-04 07:23:32 +00:00
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// opens a URL in the user's browser.
/// </summary>
/// <param name="url">the url to open</param>
2020-09-04 07:23:32 +00:00
private void OpenUrl ( string url ) {
// https://github.com/dotnet/runtime/issues/17938
if ( Environment . OSVersion . Platform = = PlatformID . Win32NT ) {
2021-06-15 00:20:46 +00:00
var psi = new ProcessStartInfo {
2020-09-04 07:23:32 +00:00
FileName = url ,
2021-06-15 00:20:46 +00:00
UseShellExecute = true ,
2020-09-04 07:23:32 +00:00
} ;
Process . Start ( psi ) ;
2021-06-15 00:34:11 +00:00
} else {
2020-09-04 07:23:32 +00:00
// let's hope you have xdg-open installed
Process . Start ( "xdg-open" , url ) ;
}
2020-09-03 05:23:23 +00:00
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// a simple MessageDialog constructor.
/// </summary>
/// <param name="message">the MessageDialog's format</param>
/// <param name="buttonsType">the MessageDialog's bt</param>
/// <returns></returns>
2020-09-05 04:15:50 +00:00
private MessageDialog MsgBox ( string message , ButtonsType buttonsType = ButtonsType . OkCancel ) {
2020-09-04 09:10:55 +00:00
var md = new MessageDialog (
parent_window : this ,
flags : DialogFlags . DestroyWithParent | DialogFlags . Modal ,
type : MessageType . Question ,
2020-09-05 04:15:50 +00:00
bt : buttonsType ,
2020-09-04 09:10:55 +00:00
format : message
2021-06-15 00:34:11 +00:00
) { KeepAbove = true , Resizable = false , FocusOnMap = true , Title = "Buypeeb" } ;
2020-09-04 09:10:55 +00:00
return md ;
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// show a simple entry dialogue that allows the user to enter text and either cancel or submit it.
/// </summary>
/// <param name="title">the title of the entry dialogue</param>
/// <param name="message">the prompt that should be presented to the user</param>
/// <param name="prefill">a string to prefill the input box with</param>
/// <returns></returns>
2021-06-15 00:34:11 +00:00
private ( bool accepted , string response ) EntryDialogue (
string title = "Buypeeb" , string message = "Hi there!" , string prefill = null
) {
2021-06-15 00:20:46 +00:00
var ed = new Dialog (
2020-09-05 03:54:25 +00:00
title : title ,
parent : this ,
flags : DialogFlags . DestroyWithParent | DialogFlags . Modal ,
/* button_data: */ "Cancel" , ResponseType . Cancel , "OK" , ResponseType . Ok
2021-06-15 00:34:11 +00:00
) { DefaultResponse = ResponseType . Ok , KeepAbove = true } ;
2020-09-05 03:54:25 +00:00
2020-08-28 16:58:14 +00:00
2021-06-15 00:20:46 +00:00
var edLabel = new Label ( message ) ;
var edEntry = new Entry ( ) ;
2021-06-15 00:34:11 +00:00
if ( ! string . IsNullOrWhiteSpace ( prefill ) ) {
2020-09-04 08:06:45 +00:00
edEntry . Text = prefill ;
}
2021-06-15 00:34:11 +00:00
2020-08-31 14:34:31 +00:00
edEntry . ActivatesDefault = true ;
2020-08-28 16:58:14 +00:00
ed . ContentArea . PackStart ( edLabel , true , true , 2 ) ;
edLabel . Show ( ) ;
ed . ContentArea . PackStart ( edEntry , true , true , 10 ) ;
edEntry . Show ( ) ;
2020-09-04 08:06:45 +00:00
ed . ContentArea . MarginBottom = 5 ;
ed . ContentArea . MarginTop = 5 ;
ed . ContentArea . MarginStart = 5 ;
ed . ContentArea . MarginEnd = 5 ;
2020-09-04 09:20:14 +00:00
ed . MarginBottom = 5 ;
2020-09-04 08:06:45 +00:00
2021-06-15 00:34:11 +00:00
var accepted = ( ResponseType ) ed . Run ( ) ;
2021-06-15 00:20:46 +00:00
var response = edEntry . Text ;
2020-09-01 07:12:17 +00:00
ed . Dispose ( ) ;
2020-08-28 16:58:14 +00:00
return ( accepted = = ResponseType . Ok , response ) ;
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// gets the sort type selected by the user - "NameDescending", "EndingAscending", etc.
/// </summary>
/// <returns>the id of the radiobutton without the "Sort" prefix</returns>
2020-09-06 04:39:30 +00:00
private string GetSortType ( ) {
2021-06-15 00:34:11 +00:00
foreach ( var name in new List < string > {
"NameDescending" , "NameAscending" , "PriceDescending" , "PriceAscending" , "EndingDescending" , "EndingAscending"
} ) {
var radio = ( RadioMenuItem ) builder . GetObject ( $"Sort{name}" ) ;
2020-09-06 04:39:30 +00:00
if ( radio . Active ) {
return name ;
}
}
return "NameAscending" ;
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// clears the treeview's liststore and adds everything in the watchlist to it, obeying sort order. tries to reselect the item that the user had selected, if possible.
/// </summary>
2020-09-02 03:21:32 +00:00
private void RenderList ( ) {
2020-09-06 05:41:14 +00:00
string id = null ;
2021-06-15 00:20:46 +00:00
if ( selectedItem ! = null ) {
id = selectedItem . id ;
2020-09-06 05:41:14 +00:00
}
2021-06-15 00:20:46 +00:00
items . Clear ( ) ;
var values = settings . watchlist . Values ;
2020-09-06 04:39:30 +00:00
IOrderedEnumerable < YahooAuctionsItem > sorted ;
2021-06-15 00:20:46 +00:00
var type = GetSortType ( ) ;
2020-09-06 04:39:30 +00:00
if ( type = = "NameDescending" ) {
sorted = values . OrderByDescending ( item = > item . name ) ;
2021-06-15 00:34:11 +00:00
} else if ( type = = "NameAscending" ) {
2020-09-06 04:39:30 +00:00
sorted = values . OrderBy ( item = > item . name ) ;
2021-06-15 00:34:11 +00:00
} else if ( type = = "PriceDescending" ) {
2021-06-15 00:20:46 +00:00
sorted = values . OrderByDescending ( item = > item . Price ) ;
2021-06-15 00:34:11 +00:00
} else if ( type = = "PriceAscending" ) {
2021-06-15 00:20:46 +00:00
sorted = values . OrderBy ( item = > item . Price ) ;
2021-06-15 00:34:11 +00:00
} else if ( type = = "EndingDescending" ) {
2020-09-06 04:39:30 +00:00
sorted = values . OrderByDescending ( item = > item . endDate ) ;
2021-06-15 00:34:11 +00:00
} else {
2020-09-06 04:39:30 +00:00
sorted = values . OrderBy ( item = > item . endDate ) ;
}
2020-09-06 16:39:56 +00:00
if ( settings . showFavouritesAtTopOfList ) {
2020-09-06 04:39:30 +00:00
foreach ( var item in sorted . Where ( item = > item . favourite ) ) {
items . AppendValues ( item ) ;
}
foreach ( var item in sorted . Where ( item = > ! item . favourite ) ) {
items . AppendValues ( item ) ;
}
2021-06-15 00:34:11 +00:00
} else {
2020-09-06 04:39:30 +00:00
foreach ( var item in sorted ) {
items . AppendValues ( item ) ;
}
2020-09-02 03:21:32 +00:00
}
2020-09-06 04:39:30 +00:00
2021-06-15 00:20:46 +00:00
( ( TreeModelFilter ) itemTreeView . Model ) . Refilter ( ) ;
2020-09-06 05:41:14 +00:00
2021-06-15 00:34:11 +00:00
if ( id = = null ) {
return ;
}
2021-06-15 00:20:46 +00:00
// attempt to reselect the item we were just looking at
// ReSharper disable once UseDeconstruction
var pathAndIter = GetRow ( id ) ;
if ( pathAndIter . path ! = null ) {
itemTreeView . Selection . SelectPath ( pathAndIter . path ) ;
2020-09-06 05:41:14 +00:00
}
2020-09-02 03:21:32 +00:00
}
2020-09-04 07:23:32 +00:00
// event handlers
2020-08-28 16:04:37 +00:00
private void ButtonAddClicked ( object sender , EventArgs a ) {
2020-09-01 09:58:35 +00:00
// Console.WriteLine("ButtonAddClicked");
2021-06-15 00:20:46 +00:00
var aid = new AddItemDialogue { Title = "Buypeeb" } ;
aid . Run ( ) ;
var url = aid . GetURL ( ) ;
var name = aid . GetName ( ) ;
2020-09-01 09:58:35 +00:00
aid . Dispose ( ) ;
// vry simpl url validation for simpol creachers
2020-09-05 07:42:51 +00:00
// TODO: better. do better.
2021-06-15 00:20:46 +00:00
var rx = new Regex ( @"^http.+yahoo.+" ) ;
2020-09-01 09:58:35 +00:00
if ( rx . IsMatch ( url ) ) {
2021-06-15 00:20:46 +00:00
UpdateItem ( settings . Watch ( url , name ) . id , true ) ;
RenderList ( ) ;
2021-06-15 00:34:11 +00:00
} else {
2021-06-15 00:20:46 +00:00
var md = MsgBox ( $"\" { url } \ " is not a valid Buyee or Yahoo! Auctions Japan URL." , ButtonsType . Ok ) ;
2020-09-05 07:40:28 +00:00
md . Run ( ) ;
md . Dispose ( ) ;
2020-09-01 09:58:35 +00:00
}
2020-08-28 16:04:37 +00:00
}
private void ButtonUpdateAllClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
UpdateItems ( ) ;
2020-08-28 16:04:37 +00:00
}
private void ButtonClearEndedClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
var md = MsgBox ( "Are you sure you want to remove all ended auctions from the list?" ) ;
2021-06-15 00:34:11 +00:00
var r = ( ResponseType ) md . Run ( ) ;
2020-09-04 13:42:22 +00:00
md . Dispose ( ) ;
if ( r ! = ResponseType . Ok ) {
return ;
2020-09-05 02:45:23 +00:00
}
2020-09-04 13:42:22 +00:00
var removeMe = new List < string > ( ) ;
2021-06-15 00:20:46 +00:00
foreach ( var ( key , value ) in settings . watchlist ) {
if ( ! value . Available ) {
removeMe . Add ( key ) ;
2020-09-05 02:45:23 +00:00
}
}
2020-09-04 13:42:22 +00:00
2020-09-05 02:45:23 +00:00
foreach ( var id in removeMe ) {
2021-06-15 00:20:46 +00:00
settings . watchlist . Remove ( id ) ;
2020-09-05 02:45:23 +00:00
}
2020-09-04 13:42:22 +00:00
2021-06-15 00:20:46 +00:00
RenderList ( ) ;
2020-08-28 16:04:37 +00:00
}
private void ButtonClearAllClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
var md = MsgBox ( "Are you sure you want to clear ALL items?" ) ;
2021-06-15 00:34:11 +00:00
if ( md . Run ( ) = = ( int ) ResponseType . Ok ) {
2021-06-15 00:20:46 +00:00
settings . watchlist . Clear ( ) ;
RenderList ( ) ;
2020-09-05 07:42:51 +00:00
}
2021-06-15 00:34:11 +00:00
2020-09-05 07:42:51 +00:00
md . Dispose ( ) ;
2020-08-28 16:04:37 +00:00
}
2020-09-05 04:15:50 +00:00
private void ButtonOpenClicked ( object sender , EventArgs a ) {
var od = new FileChooserDialog (
title : "Open userdata.json" ,
parent : this ,
action : FileChooserAction . Open ,
"Cancel" , ResponseType . Cancel , "Open" , ResponseType . Accept
) ;
var odf = new FileFilter ( ) ;
odf . Name = "JSON files" ;
odf . AddMimeType ( "application/json" ) ;
odf . AddPattern ( "*.json" ) ;
od . AddFilter ( odf ) ;
2021-06-15 00:34:11 +00:00
if ( od . Run ( ) = = ( int ) ResponseType . Accept ) {
2020-09-05 04:15:50 +00:00
try {
2021-06-15 00:20:46 +00:00
var j = File . ReadAllText ( od . Filename ) ;
settings = JsonSerializer . Deserialize < Settings > ( j ) ;
RenderList ( ) ;
UpdateItems ( ) ;
2021-06-15 00:34:11 +00:00
} catch ( Exception e ) {
2020-09-05 04:15:50 +00:00
Console . WriteLine ( e ) ;
2020-09-05 05:02:37 +00:00
var md = MsgBox ( $"Failed to load {od.Filename}!\n{e.Message}" , ButtonsType . Ok ) ;
md . Run ( ) ;
md . Dispose ( ) ;
2020-09-05 04:15:50 +00:00
}
}
2021-06-15 00:34:11 +00:00
2020-09-05 04:15:50 +00:00
od . Dispose ( ) ;
}
2020-08-28 16:04:37 +00:00
private void ButtonSaveClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
SaveSettings ( ) ;
2020-08-28 16:04:37 +00:00
}
2020-09-05 04:15:50 +00:00
private void ButtonSaveAsClicked ( object sender , EventArgs a ) {
var sd = new FileChooserDialog (
title : "Save userdata.json" ,
parent : this ,
action : FileChooserAction . Save ,
"Cancel" , ResponseType . Cancel , "Save" , ResponseType . Accept
) ;
sd . CurrentName = "userdata.json" ;
2021-06-15 00:34:11 +00:00
var sdf = new FileFilter { Name = "JSON files" } ;
2020-09-05 04:15:50 +00:00
sdf . AddMimeType ( "application/json" ) ;
sdf . AddPattern ( "*.json" ) ;
sd . AddFilter ( sdf ) ;
2021-06-15 00:34:11 +00:00
if ( sd . Run ( ) = = ( int ) ResponseType . Accept ) {
2020-09-05 04:15:50 +00:00
try {
if ( ! File . Exists ( sd . Filename ) ) {
2021-06-15 00:20:46 +00:00
using ( var fs = File . CreateText ( sd . Filename ) ) {
fs . Write ( JsonSerializer . Serialize ( settings , jsonOptions ) ) ;
2020-09-05 18:51:47 +00:00
}
2020-09-05 04:15:50 +00:00
}
2021-06-15 00:34:11 +00:00
} catch ( Exception e ) {
2020-09-05 05:02:37 +00:00
Console . WriteLine ( e ) ;
var md = MsgBox ( $"Failed to write {sd.Filename}!\n{e.Message}." , ButtonsType . Ok ) ;
md . Run ( ) ;
md . Dispose ( ) ;
}
}
sd . Dispose ( ) ;
}
private void ButtonExportClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
var readyQuery = from item in settings . watchlist . Values . ToList ( ) where ! item . Ready select item ;
2020-09-05 05:02:37 +00:00
if ( readyQuery . Count ( ) ! = 0 ) {
var md = MsgBox ( "Please wait for all items to update before exporting a CSV." , ButtonsType . Ok ) ;
md . Run ( ) ;
md . Dispose ( ) ;
return ;
}
var sd = new FileChooserDialog (
title : "Export watchlist as CSV" ,
parent : this ,
action : FileChooserAction . Save ,
"Cancel" , ResponseType . Cancel , "Save" , ResponseType . Accept
) ;
sd . CurrentName = "buypeeb.csv" ;
var sdf = new FileFilter ( ) ;
sdf . Name = "CSV files" ;
sdf . AddMimeType ( "text/csv" ) ;
sdf . AddPattern ( "*.csv" ) ;
sd . AddFilter ( sdf ) ;
2021-06-15 00:34:11 +00:00
if ( sd . Run ( ) = = ( int ) ResponseType . Accept ) {
2020-09-05 05:02:37 +00:00
try {
using ( var writer = new StreamWriter ( sd . Filename ) )
2021-06-15 00:20:46 +00:00
using ( var csv = new CsvWriter ( writer , CultureInfo . InvariantCulture ) ) {
csv . WriteRecords ( settings . watchlist ) ;
2020-09-05 05:02:37 +00:00
}
2021-06-15 00:34:11 +00:00
} catch ( Exception e ) {
2020-09-05 04:15:50 +00:00
Console . WriteLine ( e ) ;
2020-09-06 05:27:11 +00:00
var md = MsgBox ( $"Failed to write {sd.Filename}!\n{e.Message}." , ButtonsType . Ok ) ;
md . Run ( ) ;
md . Dispose ( ) ;
2020-09-05 04:15:50 +00:00
}
}
sd . Dispose ( ) ;
}
2020-09-06 05:27:11 +00:00
private void ButtonHelpClicked ( object sender , EventArgs args ) {
Console . WriteLine ( "Watchlist:" ) ;
2021-06-15 00:20:46 +00:00
foreach ( var item in settings . watchlist ) {
2020-09-06 05:27:11 +00:00
Console . WriteLine ( item ) ;
}
2021-06-15 00:34:11 +00:00
2020-09-06 05:27:11 +00:00
Console . WriteLine ( "---\nFilter results:" ) ;
2021-06-15 00:20:46 +00:00
foreach ( var item in filterQuery ) {
2020-09-06 05:27:11 +00:00
Console . WriteLine ( item ) ;
}
2021-06-15 00:34:11 +00:00
2020-09-06 05:27:11 +00:00
Console . WriteLine ( "---\nListstore contents:" ) ;
2021-06-15 00:20:46 +00:00
foreach ( object [ ] item in items ) {
2020-09-06 05:27:11 +00:00
Console . WriteLine ( item [ 0 ] ) ;
}
}
2020-08-28 16:04:37 +00:00
private void ButtonQuitClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
var md = MsgBox ( "Are you sure you want to quit?" ) ;
2020-09-04 08:06:45 +00:00
2021-06-15 00:34:11 +00:00
var response = ( ResponseType ) md . Run ( ) ;
2020-09-01 07:12:17 +00:00
md . Dispose ( ) ;
2020-08-28 16:04:37 +00:00
if ( response = = ResponseType . Ok ) {
2021-06-15 00:20:46 +00:00
SaveSettings ( ) ;
2020-08-28 16:04:37 +00:00
Application . Quit ( ) ;
}
}
2020-09-06 16:39:56 +00:00
private void ButtonSettingsClicked ( object sender , EventArgs args ) {
2021-06-15 00:20:46 +00:00
var win = new SettingsWindow ( settings ) ;
2020-09-06 16:39:56 +00:00
Application . AddWindow ( win ) ;
2021-06-15 00:20:46 +00:00
win . DeleteEvent + = WindowSettingsClosed ;
2020-09-06 16:39:56 +00:00
win . Show ( ) ;
}
2020-09-04 07:23:32 +00:00
private void TreeViewItemsSelectionChanged ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
UpdateSelectionView ( ) ;
2020-08-28 16:04:37 +00:00
}
private void ButtonViewBuyeeClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
OpenUrl ( selectedItem . buyeeUrl ) ;
2020-08-28 16:04:37 +00:00
}
private void ButtonViewYahooClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
OpenUrl ( selectedItem . url ) ;
2020-08-28 16:04:37 +00:00
}
private void ButtonSelectedRemoveClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
var item = selectedItem ;
2020-09-04 08:06:45 +00:00
2021-06-15 00:34:11 +00:00
var md = MsgBox (
$"Are you sure you want to remove the item \" { item . name } \ "?"
) ;
2020-09-04 08:06:45 +00:00
2021-06-15 00:34:11 +00:00
var response = ( ResponseType ) md . Run ( ) ;
2020-09-04 08:06:45 +00:00
md . Dispose ( ) ;
if ( response = = ResponseType . Ok ) {
2021-06-15 00:20:46 +00:00
settings . watchlist . Remove ( item . id ) ;
RenderList ( ) ;
2020-09-04 08:06:45 +00:00
}
2020-08-28 16:04:37 +00:00
}
private void ButtonSelectedRenameClicked ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
var item = selectedItem ;
2021-06-15 00:34:11 +00:00
var ( accepted , response ) = EntryDialogue (
"Rename item" , $"Enter a new name for the item \" { item . name } \ "." , item . name
) ;
2020-09-04 08:06:45 +00:00
if ( accepted ) {
item . name = response ;
2021-06-15 00:20:46 +00:00
UpdateSelectionView ( ) ;
2020-09-04 08:06:45 +00:00
}
2020-08-28 16:04:37 +00:00
}
2020-09-04 07:23:32 +00:00
private void ButtonSelectedUpdateClicked ( object sender , EventArgs args ) {
2021-06-15 00:20:46 +00:00
selectionViewBox . Sensitive = false ;
if ( updateQueue . Contains ( selectedItem . id ) ) {
2020-09-05 03:45:18 +00:00
// the item is already waiting to be updated
return ;
}
2021-06-15 00:34:11 +00:00
2021-06-15 00:20:46 +00:00
UpdateItem ( selectedItem . id ) ;
2020-09-04 07:23:32 +00:00
}
2020-09-04 11:18:51 +00:00
private void ButtonSelectedFavouriteToggled ( object sender , EventArgs args ) {
2021-06-15 00:34:11 +00:00
var s = ( ToggleButton ) sender ;
2021-06-15 00:20:46 +00:00
selectedItem . favourite = s . Active ;
2020-09-05 03:45:18 +00:00
2021-06-15 00:20:46 +00:00
if ( settings . showFavouritesAtTopOfList ) {
RenderList ( ) ;
2021-06-15 00:34:11 +00:00
} else {
2020-09-06 04:39:30 +00:00
// i don't know why this is necessary
2021-06-15 00:20:46 +00:00
var pathAndIter = GetRow ( selectedItem . id ) ;
2020-09-06 04:39:30 +00:00
if ( pathAndIter . path ! = null ) {
2021-06-15 00:20:46 +00:00
items . EmitRowChanged ( pathAndIter . path , pathAndIter . iter ) ;
2020-09-06 04:39:30 +00:00
}
2020-09-05 06:51:28 +00:00
}
2020-09-04 11:18:51 +00:00
}
2020-09-06 03:33:03 +00:00
private void ButtonSelectedNotesClearClicked ( object sender , EventArgs args ) {
2021-06-15 00:20:46 +00:00
var item = selectedItem ;
var md = MsgBox ( $"Are you sure you want to clear the notes for \" { item . name } \ "?" ) ;
2021-06-15 00:34:11 +00:00
if ( md . Run ( ) = = ( int ) ResponseType . Ok ) {
var noteBuffer = ( TextBuffer ) builder . GetObject ( "TextBufferSelectedNotes" ) ;
2020-09-06 03:33:03 +00:00
noteBuffer . Clear ( ) ;
2021-06-15 00:20:46 +00:00
selectedItem . notes = null ;
2020-09-06 03:33:03 +00:00
}
2021-06-15 00:34:11 +00:00
2020-09-06 03:33:03 +00:00
md . Dispose ( ) ;
}
private void TextViewSelectedNotesFocusOut ( object sender , FocusOutEventArgs args ) {
// the "save" button does nothing, however, when you click the save button, you transfer focus to it, firing this event!
// how very sneaky
2021-06-15 00:34:11 +00:00
var noteBuffer = ( TextBuffer ) builder . GetObject ( "TextBufferSelectedNotes" ) ;
2021-06-15 00:20:46 +00:00
if ( selectedItem ! = null ) {
2021-06-15 00:34:11 +00:00
selectedItem . notes = string . IsNullOrWhiteSpace ( noteBuffer . Text ) ? null : noteBuffer . Text ;
2020-09-06 03:33:03 +00:00
}
}
2020-09-06 04:39:30 +00:00
private void SortMenuClosed ( object sender , EventArgs args ) {
2021-06-15 00:20:46 +00:00
RenderList ( ) ;
2020-09-06 04:39:30 +00:00
}
2020-09-06 16:39:56 +00:00
private void WindowSettingsClosed ( object sender , EventArgs args ) {
2021-06-15 00:20:46 +00:00
RenderList ( ) ;
2020-09-06 16:39:56 +00:00
}
2020-09-04 11:13:40 +00:00
// timers
2020-09-06 10:35:37 +00:00
/// <summary>
/// updates the end time displayed in the selection box. runs every second to update the countdown timer.
/// </summary>
/// <returns>true</returns>
2020-09-04 11:13:40 +00:00
private bool UpdateSelectionEndTime ( ) {
2021-06-15 00:20:46 +00:00
if ( ! selectionViewBox . IsSensitive ) {
2020-09-04 11:13:40 +00:00
return true ;
}
2021-06-15 00:20:46 +00:00
var item = selectedItem ;
if ( ! item . Ready ) {
2020-09-04 11:13:40 +00:00
return true ;
}
2021-06-15 00:20:46 +00:00
var ending = "" ;
if ( item . Available ) {
2020-09-04 11:13:40 +00:00
var now = DateTime . Now ;
var end = item . endDate . ToLocalTime ( ) ;
var span = end . Subtract ( now ) ;
if ( span . Days > 0 ) {
ending + = span . ToString ( "dd' days, '" ) ; // will format twelve days as "12 days, "
}
2021-06-15 00:34:11 +00:00
2020-09-04 11:13:40 +00:00
// timespan objects don't contain definitions for the time or date separators, so the colons need to be escaped
// `HH` doesn't exist, but `hh` behaves identically
2020-09-05 02:45:23 +00:00
// see https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings
2020-09-04 11:13:40 +00:00
ending + = span . ToString ( @"hh\:mm\:ss" ) ;
2021-06-15 00:34:11 +00:00
} else {
2020-09-04 11:13:40 +00:00
ending = "Auction has ended" ;
}
2021-06-15 00:20:46 +00:00
endingLabel . Text = ending ;
2020-09-04 11:13:40 +00:00
return true ;
}
2020-09-06 10:35:37 +00:00
/// <summary>
/// updates all items that need updating. runs every ten seconds.
/// </summary>
/// <returns>true</returns>
2020-09-05 18:36:17 +00:00
private bool AutoUpdateItems ( ) {
2021-06-15 00:20:46 +00:00
if ( queueActive ) {
2020-09-05 18:36:17 +00:00
// don't autoupdate if the queue is active
return true ;
}
2021-06-15 00:20:46 +00:00
foreach ( var item in outdatedItemQuery ) {
2020-09-05 18:36:17 +00:00
updateQueue . Enqueue ( item . id ) ;
}
2021-06-15 00:20:46 +00:00
if ( updateQueue . TryPeek ( out var _ ) ) {
2020-09-05 18:36:17 +00:00
// there's at least one item in the queue
2021-06-15 00:20:46 +00:00
ProcessUpdateQueue ( ) ;
2020-09-05 18:36:17 +00:00
}
return true ;
}
2020-09-03 06:02:56 +00:00
// column renderers
2021-06-15 00:34:11 +00:00
private static void RenderColumnFavourite (
TreeViewColumn column , CellRenderer cell , ITreeModel model , TreeIter iter
) {
var item = ( YahooAuctionsItem ) model . GetValue ( iter , 0 ) ;
2021-06-15 00:20:46 +00:00
( ( CellRendererText ) cell ) . Text = item . favourite ? "♥" : "" ;
2020-09-05 03:45:18 +00:00
}
2021-06-15 00:34:11 +00:00
private static void RenderColumnName ( TreeViewColumn column , CellRenderer cell , ITreeModel model , TreeIter iter ) {
var item = ( YahooAuctionsItem ) model . GetValue ( iter , 0 ) ;
2021-06-15 00:20:46 +00:00
( ( CellRendererText ) cell ) . Text = item . name ? ? "Loading..." ;
2020-09-03 06:02:56 +00:00
}
2021-06-15 00:34:11 +00:00
private static void RenderColumnPriceYen (
TreeViewColumn column , CellRenderer cell , ITreeModel model , TreeIter iter
) {
var item = ( YahooAuctionsItem ) model . GetValue ( iter , 0 ) ;
2021-06-15 00:20:46 +00:00
( ( CellRendererText ) cell ) . Text = item . Ready ? item . priceJpy : "..." ;
2020-09-03 06:02:56 +00:00
}
2021-06-15 00:34:11 +00:00
private static void RenderColumnPriceAUD (
TreeViewColumn column , CellRenderer cell , ITreeModel model , TreeIter iter
) {
var item = ( YahooAuctionsItem ) model . GetValue ( iter , 0 ) ;
2021-06-15 00:20:46 +00:00
( ( CellRendererText ) cell ) . Text = item . Ready ? item . priceAud : "..." ;
2020-09-03 06:02:56 +00:00
}
2021-06-15 00:20:46 +00:00
private void RenderColumnEnding ( TreeViewColumn column , CellRenderer cell , ITreeModel model , TreeIter iter ) {
2021-06-15 00:34:11 +00:00
var item = ( YahooAuctionsItem ) model . GetValue ( iter , 0 ) ;
2021-06-15 00:20:46 +00:00
var ending = "" ;
if ( item . Ready ) {
if ( ! item . Available ) {
2020-09-04 13:27:56 +00:00
ending = "Ended" ;
2021-06-15 00:34:11 +00:00
} else {
2020-09-04 13:27:56 +00:00
var now = DateTime . Now ;
var end = item . endDate . ToLocalTime ( ) ;
// TODO: should we show the year if the auction ends next year? 0uo
2020-09-05 02:45:23 +00:00
if ( end . DayOfYear ! = now . DayOfYear ) {
2020-09-04 13:27:56 +00:00
// the auction isn't ending today, so we should show the day it's ending on for clarity
ending + = end . ToString ( "MMM d " ) ;
}
2021-06-15 00:34:11 +00:00
2020-09-04 13:27:56 +00:00
ending + = end . ToString ( "HH:mm" ) ;
2021-06-15 00:20:46 +00:00
if ( settings . showSecondsInListView ) {
2020-09-04 13:27:56 +00:00
// add the seconds on to the end
ending + = end . ToString ( ":ss" ) ;
}
2020-09-04 10:16:42 +00:00
}
}
2021-06-15 00:34:11 +00:00
2021-06-15 00:20:46 +00:00
( ( CellRendererText ) cell ) . Text = item . Ready ? ending : "..." ;
2020-09-03 06:02:56 +00:00
}
2020-09-05 06:51:28 +00:00
// tree filter
private bool ItemFilter ( ITreeModel model , TreeIter iter ) {
2021-06-15 00:34:11 +00:00
var item = ( YahooAuctionsItem ) model . GetValue ( iter , 0 ) ;
2020-09-06 05:27:11 +00:00
if ( item = = null ) {
return true ;
}
2021-06-15 00:34:11 +00:00
2021-06-15 00:20:46 +00:00
if ( item . Price = = 0 ) {
2020-09-06 05:27:11 +00:00
// the item has only just been added, so we can't compare it against the filters, since it's missing most of its data
return true ;
}
2020-09-05 06:51:28 +00:00
bool Filtered ( string name ) {
2021-06-15 00:20:46 +00:00
return filterChecks [ name ] . Active ;
2020-09-05 06:51:28 +00:00
}
// first, check to see if any filters are set that would exclude everything, such as hiding both active and ended auctions
// if so, there's no need to run the more expensive linq query
if (
( Filtered ( "Favourites" ) & & Filtered ( "NonFavourites" ) ) | |
( Filtered ( "Active" ) & & Filtered ( "Ended" ) ) | |
( Filtered ( "EndingToday" ) & & Filtered ( "EndingAfterToday" ) ) | |
( Filtered ( "WithWinPrice" ) & & Filtered ( "WithNoWinPrice" ) )
) {
return false ;
}
2021-06-15 00:20:46 +00:00
return filterQuery . Contains ( item ) ;
2020-09-05 06:51:28 +00:00
}
private void RunFilter ( object sender , EventArgs a ) {
2021-06-15 00:20:46 +00:00
( ( TreeModelFilter ) itemTreeView . Model ) . Refilter ( ) ;
2020-09-05 06:51:28 +00:00
}
2020-08-28 16:04:37 +00:00
}
}