Compare commits

..

No commits in common. "106d4d0ed8bd152315a085fd80244645bb77ec3d" and "04f8e4dbdb68ef8acee9fe0e14c5d570322f70db" have entirely different histories.

3 changed files with 99 additions and 94 deletions

View file

@ -26,16 +26,26 @@ using System.Threading.Tasks;
using System.Net; using System.Net;
using System.Diagnostics; using System.Diagnostics;
using Gtk; using Gtk;
using System.Linq;
namespace Buypeeb { namespace Buypeeb {
public struct ItemColumns {
public const int Name = 0;
public const int PriceYen = 1;
public const int PriceAUD = 2;
public const int Ending = 3;
public const int Id = 4;
}
class MainWindow : Window { class MainWindow : Window {
private string location; private string location;
// private Queue<string> statuses;
private ListStore items; private ListStore items;
private Settings settings; private Settings settings;
private TreeView itemTreeView; private TreeView itemTreeView;
private Label statusLabel;
private Builder builder; private Builder builder;
// TODO: whenever we get something from the builder, cache it for later // TODO: whenever we get something from the builder, cache it for later
@ -44,12 +54,10 @@ namespace Buypeeb {
private Box selectionViewBox; private Box selectionViewBox;
private Label endingLabel; private Label endingLabel;
private bool queueActive;
// ...to here. // ...to here.
static SemaphoreSlim taskLimit = new SemaphoreSlim(6); static SemaphoreSlim taskLimit = new SemaphoreSlim(4);
private Queue<string> updateQueue = new Queue<string>();
private YahooAuctionsItem selectedItem { private YahooAuctionsItem selectedItem {
get { get {
@ -97,6 +105,7 @@ namespace Buypeeb {
this.builder = builder; this.builder = builder;
builder.Autoconnect(this); builder.Autoconnect(this);
this.statusLabel = (Label)builder.GetObject("LabelStatus");
this.selectionViewBox = (Box)builder.GetObject("SelectionViewBox"); this.selectionViewBox = (Box)builder.GetObject("SelectionViewBox");
this.endingLabel = (Label)builder.GetObject("LabelSelectedEnding"); this.endingLabel = (Label)builder.GetObject("LabelSelectedEnding");
@ -107,7 +116,6 @@ namespace Buypeeb {
this.itemTreeView.Model = this.items; this.itemTreeView.Model = this.items;
TreeCellDataFunc[] funcs = { TreeCellDataFunc[] funcs = {
new TreeCellDataFunc(this.RenderColumnFavourite),
new TreeCellDataFunc(this.RenderColumnName), new TreeCellDataFunc(this.RenderColumnName),
new TreeCellDataFunc(this.RenderColumnPriceYen), new TreeCellDataFunc(this.RenderColumnPriceYen),
new TreeCellDataFunc(this.RenderColumnPriceAUD), new TreeCellDataFunc(this.RenderColumnPriceAUD),
@ -133,6 +141,10 @@ namespace Buypeeb {
// general behaviour // general behaviour
private void SetStatus(string status) {
this.statusLabel.Text = status ?? "Buypeeb";
}
private (TreePath path, TreeIter iter) GetRow(string id) { private (TreePath path, TreeIter iter) GetRow(string id) {
// TODO: surely there's a better way to do this // TODO: surely there's a better way to do this
TreeIter iter; TreeIter iter;
@ -168,7 +180,7 @@ namespace Buypeeb {
private void UpdateThread(string id) { private void UpdateThread(string id) {
var item = this.settings.watchlist[id]; var item = this.settings.watchlist[id];
// Console.WriteLine($"Updating {id}..."); Console.WriteLine($"Updating {id}...");
// set item.ready to false to show that it's still being updated // 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 // this changes a few behaviours, such as displaying the price as "..." instead of whatever's currently stored
item.ready = false; item.ready = false;
@ -195,31 +207,17 @@ namespace Buypeeb {
}); });
item.ready = true; item.ready = true;
// Console.WriteLine($"{id} updated."); Console.WriteLine($"{id} updated.");
} }
private void ProcessUpdateQueue() { private void UpdateItem(string id, bool force = false) {
// recursively process the updatequeue // don't start a new task if there are more than [tasklimit] tasks currently running
// this is a BLOCKING FUNCTION // this makes sure we don't make 1000 simultaneous requests to yahoo auctions if there are 1000 items on the watchlist
this.queueActive = true; if (this.settings.watchlist[id].updatedRecently && !force) {
this.UpdateItem(this.updateQueue.Dequeue());
if (this.updateQueue.TryPeek(out string _)) {
this.ProcessUpdateQueue();
}
else {
this.queueActive = false;
}
}
private void UpdateItem(string id, bool evenIfAlreadyUpdating = false, bool force = false) {
var item = this.settings.watchlist[id];
if (item.updatedRecently && !force) {
// the item has been updated recently, and force is not true // the item has been updated recently, and force is not true
return; return;
} }
// 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
taskLimit.Wait(); taskLimit.Wait();
var t = Task.Factory.StartNew(() => { var t = Task.Factory.StartNew(() => {
this.UpdateThread(id); this.UpdateThread(id);
@ -227,28 +225,12 @@ namespace Buypeeb {
} }
private void UpdateItems() { private void UpdateItems() {
if (this.queueActive) {
return;
}
this.selectionViewBox.Sensitive = false; this.selectionViewBox.Sensitive = false;
foreach (var item in this.settings.watchlist) {
if (!this.updateQueue.Contains(item.Key)) {
// set everything to not ready first
// ensures other actions don't attempt to display the items even if UpdateItem hasn't started yet
item.Value.ready = false;
this.updateQueue.Enqueue(item.Key);
}
}
if (!this.updateQueue.TryPeek(out string _)) {
// queue is empty
return;
}
this.itemTreeView.QueueDraw();
var t = Task.Factory.StartNew(() => { var t = Task.Factory.StartNew(() => {
this.ProcessUpdateQueue(); foreach (var item in this.settings.watchlist) {
this.UpdateItem(item.Key);
}
}); });
} }
@ -269,19 +251,20 @@ namespace Buypeeb {
this.selectionViewBox.Sensitive = item.ready; this.selectionViewBox.Sensitive = item.ready;
infobox.Visible = true; infobox.Visible = true;
var info = new Dictionary<string, string> { var info = new Dictionary<string, string>
{ "Name", item.name }, {
{ "YahooName", item.originalName }, { "Name", item.name },
{ "Price", item.PriceJPY() }, { "YahooName", item.originalName },
{ "PriceAUD", item.PriceAUD() }, { "Price", item.PriceJPY() },
{ "Ending", "..." }, { "PriceAUD", item.PriceAUD() },
{ "Bids", $"{item.bids}" }, { "Ending", "..." },
{ "BuyItNow", item.winPrice == 0 ? "No" : $"¥{item.PriceJPY(true)} (${item.PriceAUD(true)})" }, { "Bids", $"{item.bids}" },
{ "AutoExtension", item.autoExtension ? "Yes" : "No" }, { "BuyItNow", item.winPrice == 0 ? "No" : $"¥{item.PriceJPY(true)} (${item.PriceAUD(true)})" },
{ "LastUpdated", "Last updated: heeeenlo" } { "AutoExtension", item.autoExtension ? "Yes" : "No" },
}; { "LastUpdated", "Last updated: heeeenlo" }
};
foreach (var row in info) { foreach (var row in info) {
var l = (Label)this.builder.GetObject($"LabelSelected{row.Key}"); var l = (Label)this.builder.GetObject($"LabelSelected{row.Key}");
l.Text = row.Value; l.Text = row.Value;
} }
@ -392,18 +375,18 @@ namespace Buypeeb {
md.Dispose(); md.Dispose();
if (r != ResponseType.Ok) { if (r != ResponseType.Ok) {
return; return;
} }
var removeMe = new List<string>(); var removeMe = new List<string>();
foreach (var item in this.settings.watchlist) { foreach(var item in this.settings.watchlist) {
if (!item.Value.available) { if (!item.Value.available) {
removeMe.Add(item.Key); removeMe.Add(item.Key);
} }
} }
foreach (var id in removeMe) { foreach(var id in removeMe) {
this.settings.watchlist.Remove(id); this.settings.watchlist.Remove(id);
} }
this.RenderList(); this.RenderList();
} }
@ -463,20 +446,12 @@ namespace Buypeeb {
private void ButtonSelectedUpdateClicked(object sender, EventArgs args) { private void ButtonSelectedUpdateClicked(object sender, EventArgs args) {
this.selectionViewBox.Sensitive = false; this.selectionViewBox.Sensitive = false;
if (this.updateQueue.Contains(this.selectedItem.id)) {
// the item is already waiting to be updated
return;
}
this.UpdateItem(this.selectedItem.id); this.UpdateItem(this.selectedItem.id);
} }
private void ButtonSelectedFavouriteToggled(object sender, EventArgs args) { private void ButtonSelectedFavouriteToggled(object sender, EventArgs args) {
ToggleButton s = (ToggleButton)sender; ToggleButton s = (ToggleButton)sender;
this.selectedItem.favourite = s.Active; this.selectedItem.favourite = s.Active;
// i don't know why this is necessary
var pathAndIter = this.GetRow(this.selectedItem.id);
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
} }
// timers // timers
@ -502,7 +477,7 @@ namespace Buypeeb {
} }
// timespan objects don't contain definitions for the time or date separators, so the colons need to be escaped // 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 // `HH` doesn't exist, but `hh` behaves identically
// see https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings // see https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings
ending += span.ToString(@"hh\:mm\:ss"); ending += span.ToString(@"hh\:mm\:ss");
} }
else { else {
@ -515,11 +490,6 @@ namespace Buypeeb {
// column renderers // column renderers
private void RenderColumnFavourite(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) {
YahooAuctionsItem item = (YahooAuctionsItem)model.GetValue(iter, 0);
(cell as Gtk.CellRendererText).Text = item.favourite ? "♥" : "";
}
private void RenderColumnName(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) { private void RenderColumnName(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) {
YahooAuctionsItem item = (YahooAuctionsItem)model.GetValue(iter, 0); YahooAuctionsItem item = (YahooAuctionsItem)model.GetValue(iter, 0);
(cell as Gtk.CellRendererText).Text = item.name ?? "Loading..."; (cell as Gtk.CellRendererText).Text = item.name ?? "Loading...";
@ -541,17 +511,18 @@ namespace Buypeeb {
if (item.ready) { if (item.ready) {
if (!item.available) { if (!item.available) {
ending = "Ended"; ending = "Ended";
} } else {
else {
var now = DateTime.Now; var now = DateTime.Now;
var end = item.endDate.ToLocalTime(); var end = item.endDate.ToLocalTime();
// TODO: should we show the year if the auction ends next year? 0uo // TODO: should we show the year if the auction ends next year? 0uo
if (end.DayOfYear != now.DayOfYear) { if (end.DayOfYear != now.DayOfYear)
{
// the auction isn't ending today, so we should show the day it's ending on for clarity // the auction isn't ending today, so we should show the day it's ending on for clarity
ending += end.ToString("MMM d "); ending += end.ToString("MMM d ");
} }
ending += end.ToString("HH:mm"); ending += end.ToString("HH:mm");
if (this.settings.displaySecondsInList) { if (this.settings.displaySecondsInList)
{
// add the seconds on to the end // add the seconds on to the end
ending += end.ToString(":ss"); ending += end.ToString(":ss");
} }

View file

@ -50,6 +50,7 @@ namespace Buypeeb {
public YahooAuctionsItem(string id, string name) { public YahooAuctionsItem(string id, string name) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.ready = false;
} }
public YahooAuctionsItem() { public YahooAuctionsItem() {

View file

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 --> <!-- Generated with glade 3.22.2 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.22"/>
<object class="GtkTextBuffer" id="TextBufferSelectedNotes"/> <object class="GtkTextBuffer" id="TextBufferSelectedNotes"/>
<object class="GtkWindow" id="wndMain"> <object class="GtkWindow" id="wndMain">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="default_width">810</property> <property name="default_width">700</property>
<child type="titlebar">
<placeholder/>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
@ -112,6 +115,7 @@
<child> <child>
<object class="GtkToolButton" id="ButtonClearEnded"> <object class="GtkToolButton" id="ButtonClearEnded">
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Clear ended</property> <property name="tooltip_text" translatable="yes">Clear ended</property>
<property name="label" translatable="yes">Clear ended</property> <property name="label" translatable="yes">Clear ended</property>
@ -168,6 +172,7 @@
<child> <child>
<object class="GtkToolButton"> <object class="GtkToolButton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Save</property> <property name="tooltip_text" translatable="yes">Save</property>
<property name="label" translatable="yes">Save</property> <property name="label" translatable="yes">Save</property>
@ -282,19 +287,11 @@
<signal name="changed" handler="TreeViewItemsSelectionChanged" swapped="no"/> <signal name="changed" handler="TreeViewItemsSelectionChanged" swapped="no"/>
</object> </object>
</child> </child>
<child>
<object class="GtkTreeViewColumn">
<property name="title" translatable="yes">♥</property>
<child>
<object class="GtkCellRendererText"/>
</child>
</object>
</child>
<child> <child>
<object class="GtkTreeViewColumn"> <object class="GtkTreeViewColumn">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="sizing">fixed</property> <property name="sizing">fixed</property>
<property name="fixed_width">100</property> <property name="fixed_width">50</property>
<property name="title" translatable="yes">Name</property> <property name="title" translatable="yes">Name</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="clickable">True</property> <property name="clickable">True</property>
@ -610,7 +607,6 @@
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child> <child>
@ -811,10 +807,47 @@
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_start">5</property>
<property name="margin_end">5</property>
<property name="margin_top">3</property>
<property name="margin_bottom">3</property>
<child>
<object class="GtkLabel" id="LabelStatus">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Tracking 3 items</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object> </object>
</child> </child>
<child type="titlebar">
<placeholder/>
</child>
</object> </object>
</interface> </interface>