/* 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 . */ using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Gtk; 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 { private string location; private ListStore items; private Settings settings; private TreeView itemTreeView; private Label statusLabel; static SemaphoreSlim tasklimit = new SemaphoreSlim(4); public MainWindow() : this(new Builder("main.glade")) { } private MainWindow(Builder builder) : base(builder.GetObject("wndMain").Handle) { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { // C:\Users\Beebus\AppData\Roaming\Lynnear Software\buypeeb this.location = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%APPDATA%"), "Lynnear Software", "buypeeb"); } else { // ~/.config/Lynnear Software/buypeeb this.location = System.IO.Path.Combine(Environment.GetEnvironmentVariable("HOME"), ".config", "Lynnear Software", "buypeeb"); } string userdata = System.IO.Path.Combine(location, "userdata.json"); if (File.Exists(userdata)) { try { string j = File.ReadAllText(userdata); this.settings = JsonSerializer.Deserialize(j); } catch { // ??? Console.WriteLine("oops"); Application.Quit(); } } else { this.settings = new Settings(); } this.SaveSettings(); this.Title = "Buypeeb"; builder.Autoconnect(this); this.statusLabel = (Label)builder.GetObject("LabelStatus"); // bind treeview columns to watchlist instead of needing to manually sync its liststore this.itemTreeView = (TreeView)builder.GetObject("TreeViewItems"); this.items = new ListStore(typeof(Listing)); this.RenderList(); this.itemTreeView.Model = this.items; for (int i = 0; i < this.itemTreeView.Columns.Length; i++) { var c = this.itemTreeView.Columns[i]; TreeCellDataFunc func; //TODO: get rid of this awful if statement if (i == ItemColumns.Name) { func = new TreeCellDataFunc(this.RenderColumnName); } else if (i == ItemColumns.PriceYen) { func = new TreeCellDataFunc(this.RenderColumnPriceYen); } else if (i == ItemColumns.PriceAUD) { func = new TreeCellDataFunc(this.RenderColumnPriceAUD); } else if (i == ItemColumns.Ending) { func = new TreeCellDataFunc(this.RenderColumnEnding); } else { Console.WriteLine($"unexpected value {i}!"); throw new IndexOutOfRangeException(); } c.SetCellDataFunc(c.Cells[0], func); } foreach (object[] row in this.items) { Console.WriteLine(row[0]); } DeleteEvent += Window_Shutdown; } private void Window_Shutdown(object sender, DeleteEventArgs args) { Application.Quit(); } // general behaviour private void SetStatus(string status) { this.statusLabel.Text = status ?? "Buypeeb"; } private void SaveSettings() { string j = JsonSerializer.Serialize(this.settings); string p = System.IO.Path.Combine(this.location, "userdata.json"); Console.WriteLine(j); if (!File.Exists(p)) { File.CreateText(p); } File.WriteAllText(System.IO.Path.Combine(this.location, "userdata.json"), j); } private void UpdateThread(string id) { var item = this.settings.watchlist[id]; Console.WriteLine($"Updating {id}..."); // 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 item.ready = false; // TODO: actually download the data Thread.Sleep(3000); string html = "Heeenlo"; item.name = html; item.ready = true; Console.WriteLine($"{id} updated."); Gtk.Application.Invoke(delegate { // TODO: surely there's a better way to do this TreeIter iter; this.itemTreeView.Model.GetIterFirst(out iter); for (int i = 0; i < this.itemTreeView.Model.IterNChildren(); i++) { var x = (Listing)this.itemTreeView.Model.GetValue(iter, 0); if (x.id == id) { this.items.EmitRowChanged(this.itemTreeView.Model.GetPath(iter), iter); break; } else { this.itemTreeView.Model.IterNext(ref iter); } } }); } private void UpdateItem(string id) { // 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(); var t = Task.Factory.StartNew(() => { this.UpdateThread(id); }).ContinueWith(task => { tasklimit.Release(); }); } private void UpdateItems() { var t = Task.Factory.StartNew(() => { foreach (var item in this.settings.watchlist) { this.UpdateItem(item.Key); } }); } // show a simple entry dialogue that allows the user to enter text and either cancel or submit it private (Boolean accepted, string response) EntryDialogue(string title = "Buypeeb", string message = "Hi there!") { Dialog ed = new Dialog(title, null, Gtk.DialogFlags.DestroyWithParent, "Cancel", ResponseType.Cancel, "OK", ResponseType.Ok); ed.DefaultResponse = ResponseType.Ok; Label edLabel = new Label(message); Entry edEntry = new Entry(); edEntry.ActivatesDefault = true; ed.ContentArea.PackStart(edLabel, true, true, 2); edLabel.Show(); ed.ContentArea.PackStart(edEntry, true, true, 10); edEntry.Show(); ResponseType accepted = (ResponseType)ed.Run(); string response = edEntry.Text; ed.Dispose(); return (accepted == ResponseType.Ok, response); } private void RenderList() { this.items.Clear(); foreach (var item in this.settings.watchlist) { items.AppendValues(item.Value); } } // button handlers private void ButtonAddClicked(object sender, EventArgs a) { // Console.WriteLine("ButtonAddClicked"); AddItemDialogue aid = new AddItemDialogue(); ResponseType accepted = (ResponseType)aid.Run(); string url = aid.GetURL(); string name = aid.GetName(); aid.Dispose(); // vry simpl url validation for simpol creachers Regex rx = new Regex(@"^http.+yahoo.+"); if (rx.IsMatch(url)) { Console.WriteLine("{0} will be added", url); this.settings.Watch(url, name); this.RenderList(); } else { Console.WriteLine("{0} is an invalid url", url); } } private void ButtonUpdateAllClicked(object sender, EventArgs a) { this.UpdateItems(); } private void ButtonClearEndedClicked(object sender, EventArgs a) { Console.WriteLine("ButtonClearEndedClicked"); } private void ButtonClearAllClicked(object sender, EventArgs a) { Console.WriteLine("ButtonClearAllClicked"); } private void ButtonSaveClicked(object sender, EventArgs a) { Console.WriteLine("ButtonSaveClicked"); } private void ButtonQuitClicked(object sender, EventArgs a) { MessageDialog md = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.OkCancel, "Are you sure you want to quit?"); ResponseType response = (ResponseType)md.Run(); md.Dispose(); if (response == ResponseType.Ok) { Application.Quit(); } } private void tveItemsSelectionChanged(object sender, EventArgs a) { Console.WriteLine("tveItemsSelectionChanged"); } private void ButtonViewBuyeeClicked(object sender, EventArgs a) { Console.WriteLine("ButtonViewBuyeeClicked"); } private void ButtonViewYahooClicked(object sender, EventArgs a) { Console.WriteLine("ButtonViewYahooClicked"); } private void ButtonSelectedRemoveClicked(object sender, EventArgs a) { Console.WriteLine("ButtonSelectedRemoveClicked"); } private void ButtonSelectedRenameClicked(object sender, EventArgs a) { Console.WriteLine("ButtonSelectedRenameClicked"); } // column renderers private void RenderColumnName(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) { Listing item = (Listing)model.GetValue(iter, 0); (cell as Gtk.CellRendererText).Text = item.name ?? "Loading..."; } private void RenderColumnPriceYen(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) { Listing item = (Listing)model.GetValue(iter, 0); (cell as Gtk.CellRendererText).Text = item.PriceJPY(); } private void RenderColumnPriceAUD(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) { Listing item = (Listing)model.GetValue(iter, 0); (cell as Gtk.CellRendererText).Text = item.PriceAUD(); } private void RenderColumnEnding(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) { Listing item = (Listing)model.GetValue(iter, 0); (cell as Gtk.CellRendererText).Text = "whatever"; } } }