Make instance vars to be pointers
[lsnes.git] / src / platform / wxwidgets / window-fileupload.cpp
blob29bff0f19b1c0307215487e1928cdeda486ac29b
1 #include "platform/wxwidgets/platform.hpp"
2 #include "platform/wxwidgets/menu_upload.hpp"
3 #include "core/fileupload.hpp"
4 #include "core/instance.hpp"
5 #include "core/misc.hpp"
6 #include "core/random.hpp"
7 #include "core/rom.hpp"
8 #include "core/project.hpp"
9 #include "core/moviedata.hpp"
10 #include "library/directory.hpp"
11 #include "library/skein.hpp"
12 #include "library/zip.hpp"
13 #include "library/json.hpp"
14 #include "library/string.hpp"
15 #include <fstream>
16 #include <iostream>
17 #include <iomanip>
18 #include <wx/wx.h>
19 #include <wx/event.h>
20 #include <wx/control.h>
21 #include <wx/combobox.h>
22 #include <wx/spinctrl.h>
23 #include <boost/iostreams/categories.hpp>
24 #include <boost/iostreams/copy.hpp>
25 #include <boost/iostreams/stream.hpp>
26 #include <boost/iostreams/stream_buffer.hpp>
27 #include <boost/iostreams/filter/symmetric.hpp>
28 #include <boost/iostreams/filter/zlib.hpp>
29 #include <boost/iostreams/filtering_stream.hpp>
30 #include <boost/iostreams/device/back_inserter.hpp>
32 #define NO_GAME_NAME "(default)"
34 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir);
36 namespace
38 const std::string& str_tolower(const std::string& x)
40 static std::map<std::string, std::string> cache;
41 if(!cache.count(x)) {
42 std::ostringstream y;
43 for(auto i : x) {
44 if(i >= 'A' && i <= 'Z')
45 y << (char)(i + 32);
46 else
47 y << (char)i;
49 cache[x] = y.str();
51 return cache[x];
53 inline bool is_prefix(std::string a, std::string b)
55 return (a.length() <= b.length() && b.substr(0, a.length()) == a);
58 bool search_match(const std::string& term, const std::string& name)
60 std::set<std::string> searched;
61 std::vector<std::string> name_words;
62 for(auto i : token_iterator_foreach<char>(name, {" "})) if(i != "") name_words.push_back(str_tolower(i));
63 for(auto i : token_iterator_foreach<char>(term, {" "})) {
64 if(i == "")
65 continue;
66 std::string st = str_tolower(i);
67 if(searched.count(st))
68 continue;
69 for(size_t j = 0; j < name_words.size(); j++) {
70 if(is_prefix(st, name_words[j]))
71 goto out;
73 return false;
74 out:
75 searched.insert(st);
77 return true;
80 class wxwin_gameselect : public wxDialog
82 public:
83 wxwin_gameselect(wxWindow* parent, const std::list<std::string>& _choices, const std::string& dflt,
84 const std::string& system, int x, int y)
85 : wxDialog(parent, wxID_ANY, wxT("lsnes: Pick a game"), wxPoint(x, y)),
86 chosen(dflt), choices(_choices)
88 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
89 SetSizer(top_s);
91 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Search:")), 0, wxGROW);
92 top_s->Add(search = new wxTextCtrl(this, wxID_ANY, wxT("")), 1, wxGROW);
93 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW);
94 top_s->Add(games = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(400, 300)), 1, wxGROW);
96 search->Connect(wxEVT_COMMAND_TEXT_UPDATED,
97 wxCommandEventHandler(wxwin_gameselect::on_search_type), NULL, this);
98 games->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
99 wxCommandEventHandler(wxwin_gameselect::on_list_select), NULL, this);
101 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
102 pbutton_s->AddStretchSpacer();
103 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
104 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
105 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
106 wxCommandEventHandler(wxwin_gameselect::on_ok), NULL, this);
107 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
108 wxCommandEventHandler(wxwin_gameselect::on_cancel), NULL, this);
109 top_s->Add(pbutton_s, 0, wxGROW);
111 auto dsplit = split_name(dflt);
112 bool wrong_default = (dflt != NO_GAME_NAME && system != "" && dsplit.first != system);
113 if(system != "" && !wrong_default) {
114 //Populate just one system.
115 rsystem = system;
116 oneplat = true;
117 } else {
118 oneplat = false;
120 wxCommandEvent e;
121 on_search_type(e);
122 games->SetStringSelection(dflt);
124 std::string get()
126 return chosen;
128 void on_search_type(wxCommandEvent& e)
130 std::string current;
131 if(games->GetSelection() != wxNOT_FOUND)
132 current = games->GetStringSelection();
133 games->Clear();
134 std::string terms = tostdstring(search->GetValue());
135 for(auto& i : choices) {
136 auto g = split_name(i);
137 if(i != current && i != NO_GAME_NAME) {
138 if(oneplat && g.first != rsystem)
139 continue; //Wrong system.
140 if(!search_match(terms, i))
141 continue; //Doesn't match terms.
143 games->Append(towxstring(i));
145 if(current != "")
146 games->SetStringSelection(current);
148 void on_list_select(wxCommandEvent& e)
150 if(games->GetSelection() != wxNOT_FOUND) {
151 chosen = tostdstring(games->GetStringSelection());
152 ok->Enable(true);
153 } else {
154 ok->Enable(false);
157 void on_ok(wxCommandEvent& e)
159 EndModal(wxID_OK);
161 void on_cancel(wxCommandEvent& e)
163 EndModal(wxID_CANCEL);
165 private:
166 std::pair<std::string, std::string> split_name(const std::string& name)
168 if(name == NO_GAME_NAME)
169 return std::make_pair("N/A", NO_GAME_NAME);
170 std::string _name = name;
171 size_t r = _name.find_first_of(" ");
172 if(r >= _name.length())
173 return std::make_pair("???", name);
174 else if(r == 0)
175 return std::make_pair("???", _name.substr(1));
176 else
177 return std::make_pair(_name.substr(0, r), _name.substr(r + 1));
179 std::string chosen;
180 const std::list<std::string>& choices;
181 wxTextCtrl* search;
182 wxListBox* games;
183 wxButton* ok;
184 wxButton* cancel;
185 std::string old_system;
186 bool oneplat;
187 std::string rsystem;
190 class wxeditor_uploadtarget : public wxDialog
192 public:
193 wxeditor_uploadtarget(wxWindow* parent);
194 wxeditor_uploadtarget(wxWindow* parent, upload_menu::upload_entry entry);
195 upload_menu::upload_entry get_entry();
196 void generate_dh25519(wxCommandEvent& e);
197 void on_ok(wxCommandEvent& e);
198 void on_cancel(wxCommandEvent& e);
199 void on_auth_sel(wxCommandEvent& e);
200 void revalidate(wxCommandEvent& e);
201 private:
202 void dh25519_fill_box();
203 void ctor_common();
204 wxButton* ok;
205 wxButton* cancel;
206 wxTextCtrl* name;
207 wxTextCtrl* url;
208 wxComboBox* auth;
209 wxPanel* dh25519_p;
210 wxTextCtrl* dh25519_k;
211 wxButton* dh25519_g;
214 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow* parent)
215 : wxDialog(parent, wxID_ANY, towxstring("lsnes: New upload target"))
217 ctor_common();
218 wxCommandEvent e;
219 on_auth_sel(e);
222 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow* parent, upload_menu::upload_entry entry)
223 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Edit upload target: " + entry.name))
225 ctor_common();
226 name->SetValue(towxstring(entry.name));
227 url->SetValue(towxstring(entry.url));
228 switch(entry.auth) {
229 case upload_menu::upload_entry::AUTH_DH25519:
230 auth->SetSelection(0);
231 wxCommandEvent e;
232 on_auth_sel(e);
233 break;
237 void wxeditor_uploadtarget::dh25519_fill_box()
239 try {
240 uint8_t rbuf[32];
241 get_dh25519_pubkey(rbuf);
242 char out[65];
243 out[64] = 0;
244 for(unsigned i = 0; i < 32; i++)
245 sprintf(out + 2 * i, "%02x", rbuf[i]);
246 dh25519_k->SetValue(towxstring(out));
247 dh25519_g->Disable();
248 } catch(...) {
249 dh25519_k->SetValue(towxstring("(Not available)"));
250 dh25519_g->Enable();
252 wxCommandEvent e;
253 revalidate(e);
256 void wxeditor_uploadtarget::ctor_common()
258 ok = NULL;
259 std::vector<wxString> auth_choices;
260 Center();
261 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
262 SetSizer(top_s);
264 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring("Name")), 0, wxGROW);
265 top_s->Add(name = new wxTextCtrl(this, wxID_ANY, towxstring(""), wxDefaultPosition, wxSize(550, -1)), 0,
266 wxGROW);
267 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring("URL")), 0, wxGROW);
268 top_s->Add(url = new wxTextCtrl(this, wxID_ANY, towxstring(""), wxDefaultPosition, wxSize(550, -1)), 0,
269 wxGROW);
270 name->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_uploadtarget::revalidate), NULL,
271 this);
272 url->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_uploadtarget::revalidate), NULL,
273 this);
275 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring("Authentication")), 0, wxGROW);
276 auth_choices.push_back(towxstring("dh25519"));
277 top_s->Add(auth = new wxComboBox(this, wxID_ANY, auth_choices[0], wxDefaultPosition, wxDefaultSize,
278 auth_choices.size(), &auth_choices[0], wxCB_READONLY), 0, wxGROW);
280 dh25519_p = new wxPanel(this, wxID_ANY);
281 wxBoxSizer* dh25519_s = new wxBoxSizer(wxVERTICAL);
282 dh25519_p->SetSizer(dh25519_s);
283 wxStaticBox* dh25519_b = new wxStaticBox(dh25519_p, wxID_ANY, towxstring("Authentication parameters"));
284 wxStaticBoxSizer* dh25519_s2 = new wxStaticBoxSizer(dh25519_b, wxVERTICAL);
285 top_s->Add(dh25519_p, 0, wxGROW);
286 dh25519_s->Add(dh25519_s2, 0, wxGROW);
287 dh25519_s2->Add(new wxStaticText(dh25519_p, wxID_ANY, towxstring("Key")), 0, wxGROW);
288 dh25519_s2->Add(dh25519_k = new wxTextCtrl(dh25519_p, wxID_ANY, towxstring(""), wxDefaultPosition,
289 wxSize(550, -1), wxTE_READONLY), 0, wxGROW);
290 dh25519_s2->Add(dh25519_g = new wxButton(dh25519_p, wxID_ANY, towxstring("Generate")), 0, wxGROW);
291 dh25519_s->SetSizeHints(dh25519_p);
292 dh25519_fill_box();
294 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
295 pbutton_s->AddStretchSpacer();
296 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
297 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
298 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
299 wxCommandEventHandler(wxeditor_uploadtarget::on_ok), NULL, this);
300 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
301 wxCommandEventHandler(wxeditor_uploadtarget::on_cancel), NULL, this);
302 top_s->Add(pbutton_s, 0, wxGROW);
304 wxCommandEvent e;
305 revalidate(e);
307 top_s->SetSizeHints(this);
308 Fit();
311 void wxeditor_uploadtarget::on_ok(wxCommandEvent& e)
313 EndModal(wxID_OK);
316 void wxeditor_uploadtarget::on_cancel(wxCommandEvent& e)
318 EndModal(wxID_CANCEL);
321 void wxeditor_uploadtarget::generate_dh25519(wxCommandEvent& e)
323 uint8_t rbuf[192];
324 try {
325 std::string entropy = pick_text(this, "Enter garbage", "Mash some garbage from keyboard to derive\n"
326 "key from:", "", true);
327 highrandom_256(rbuf + 0);
328 highrandom_256(rbuf + 32);
329 std::vector<char> x;
330 x.resize(entropy.length());
331 std::copy(entropy.begin(), entropy.end(), x.begin());
332 skein::hash h(skein::hash::PIPE_1024, 1024);
333 h.write((uint8_t*)&x[0], x.size());
334 h.read((uint8_t*)rbuf + 64);
336 std::ofstream fp(get_config_path() + "/dh25519.key", std::ios::binary);
337 if(!fp) throw std::runtime_error("Can't open keyfile");
338 #if !defined(_WIN32) && !defined(_WIN64)
339 chmod((get_config_path() + "/dh25519.key").c_str(), 0600);
340 #endif
341 fp.write((char*)rbuf, 192);
342 if(!fp) throw std::runtime_error("Can't write keyfile");
344 skein::zeroize(rbuf, sizeof(rbuf));
345 dh25519_fill_box();
346 } catch(canceled_exception& e) {
347 skein::zeroize(rbuf, sizeof(rbuf));
348 return;
349 } catch(std::exception& e) {
350 skein::zeroize(rbuf, sizeof(rbuf));
351 show_message_ok(this, "Generate keys error", std::string("Error generating keys:") + e.what(),
352 wxICON_EXCLAMATION);
353 return;
357 void wxeditor_uploadtarget::on_auth_sel(wxCommandEvent& e)
359 dh25519_p->Show(false);
360 switch(auth->GetSelection()) {
361 case 0:
362 dh25519_p->Show(true);
363 break;
365 revalidate(e);
366 Fit();
369 upload_menu::upload_entry wxeditor_uploadtarget::get_entry()
371 upload_menu::upload_entry ent;
372 ent.name = tostdstring(name->GetValue());
373 ent.url = tostdstring(url->GetValue());
374 ent.auth = upload_menu::upload_entry::AUTH_DH25519;
375 switch(auth->GetSelection()) {
376 case 0:
377 ent.auth = upload_menu::upload_entry::AUTH_DH25519;
378 break;
380 return ent;
383 void wxeditor_uploadtarget::revalidate(wxCommandEvent& e)
385 bool valid = true;
386 if(!name || (name->GetValue().Length() == 0)) valid = false;
387 std::string URL = url ? tostdstring(url->GetValue()) : "";
388 if(!regex_match("https?://(([!$&'()*+,;=:A-Za-z0-9._~-]|%[0-9A-Fa-f][0-9A-Fa-f])+|\\[v[0-9A-Fa-f]\\."
389 "([!$&'()*+,;=:A-Za-z0-9._~-]|%[0-9A-Fa-f][0-9A-Fa-f])+\\]|\\[[0-9A-Fa-f:]+\\])"
390 "(/([!$&'()*+,;=:A-Za-z0-9._~@-]|%[0-9A-Fa-f][0-9A-Fa-f])+)*", URL)) valid = false;
391 if(!auth || (auth->GetSelection() == 0 && dh25519_g->IsEnabled())) valid = false;
392 if(ok) ok->Enable(valid);
395 class wxeditor_uploadtargets : public wxDialog
397 public:
398 wxeditor_uploadtargets(wxWindow* parent, upload_menu* menu);
399 void on_ok(wxCommandEvent& e);
400 void on_add(wxCommandEvent& e);
401 void on_modify(wxCommandEvent& e);
402 void on_remove(wxCommandEvent& e);
403 void on_list_sel(wxCommandEvent& e);
404 private:
405 void refresh();
406 upload_menu* umenu;
407 std::map<int, unsigned> id_map;
408 wxListBox* list;
409 wxButton* ok;
410 wxButton* add;
411 wxButton* modify;
412 wxButton* _delete;
415 wxeditor_uploadtargets::wxeditor_uploadtargets(wxWindow* parent, upload_menu* menu)
416 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Configure upload targets"), wxDefaultPosition,
417 wxSize(400, 500))
419 umenu = menu;
420 Center();
421 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
422 SetSizer(top_s);
424 top_s->Add(list = new wxListBox(this, wxID_ANY), 1, wxGROW);
425 list->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
426 wxCommandEventHandler(wxeditor_uploadtargets::on_list_sel), NULL, this);
428 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
429 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
430 pbutton_s->AddStretchSpacer();
431 pbutton_s->Add(add = new wxButton(this, wxID_ADD, wxT("Add")), 0, wxGROW);
432 pbutton_s->Add(modify = new wxButton(this, wxID_EDIT, wxT("Modify")), 0, wxGROW);
433 pbutton_s->Add(_delete = new wxButton(this, wxID_DELETE, wxT("Delete")), 0, wxGROW);
434 modify->Enable(false);
435 _delete->Enable(false);
436 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
437 wxCommandEventHandler(wxeditor_uploadtargets::on_ok), NULL, this);
438 add->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
439 wxCommandEventHandler(wxeditor_uploadtargets::on_add), NULL, this);
440 modify->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
441 wxCommandEventHandler(wxeditor_uploadtargets::on_modify), NULL, this);
442 _delete->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
443 wxCommandEventHandler(wxeditor_uploadtargets::on_remove), NULL, this);
444 top_s->Add(pbutton_s, 0, wxGROW);
446 refresh();
447 top_s->SetSizeHints(this);
448 Fit();
451 void wxeditor_uploadtargets::on_ok(wxCommandEvent& e)
453 EndModal(wxID_OK);
456 void wxeditor_uploadtargets::on_add(wxCommandEvent& e)
458 auto f = new wxeditor_uploadtarget(this);
459 int r = f->ShowModal();
460 if(r == wxID_OK) {
461 unsigned ent = 0;
462 auto used = umenu->entries();
463 while(used.count(ent)) ent++;
464 umenu->configure_entry(ent, f->get_entry());
466 f->Destroy();
467 refresh();
470 void wxeditor_uploadtargets::on_modify(wxCommandEvent& e)
472 auto s = list->GetSelection();
473 if(s == wxNOT_FOUND) return;
474 if(!id_map.count(s)) return;
475 auto f = new wxeditor_uploadtarget(this, umenu->get_entry(id_map[s]));
476 int r = f->ShowModal();
477 if(r == wxID_OK)
478 umenu->configure_entry(id_map[s], f->get_entry());
479 f->Destroy();
480 refresh();
483 void wxeditor_uploadtargets::on_remove(wxCommandEvent& e)
485 auto s = list->GetSelection();
486 if(s == wxNOT_FOUND) return;
487 if(!id_map.count(s)) return;
488 unsigned id = id_map[s];
489 umenu->delete_entry(id);
490 refresh();
493 void wxeditor_uploadtargets::on_list_sel(wxCommandEvent& e)
495 auto s = list->GetSelection();
496 modify->Enable(s != wxNOT_FOUND);
497 _delete->Enable(s != wxNOT_FOUND);
500 void wxeditor_uploadtargets::refresh()
502 auto ents = umenu->entries();
503 auto sel = list->GetSelection();
504 auto sel_id = id_map.count(sel) ? id_map[sel] : 0xFFFFFFFFU;
506 list->Clear();
507 id_map.clear();
508 int num = 0;
509 for(auto i : ents) {
510 auto ent = umenu->get_entry(i);
511 list->Append(towxstring(ent.name));
512 id_map[num++] = i;
515 //Try to keep selection.
516 if(sel_id != 0xFFFFFFFFU) {
517 int x = wxNOT_FOUND;
518 for(auto i : id_map)
519 if(i.second == sel_id)
520 x = i.first;
521 if(x != wxNOT_FOUND)
522 list->SetSelection(x);
523 } else if(sel < (signed)list->GetCount())
524 list->SetSelection(sel);
527 class wxeditor_uploaddialog : public wxDialog
529 public:
530 wxeditor_uploaddialog(wxWindow* parent, upload_menu::upload_entry entry);
531 void on_ok(wxCommandEvent& e);
532 void on_cancel(wxCommandEvent& e);
533 void on_source_sel(wxCommandEvent& e);
534 void on_file_sel(wxCommandEvent& e);
535 void on_wclose(wxCloseEvent& e);
536 void timer_tick();
537 void on_game_sel(wxCommandEvent& e);
538 private:
539 struct _timer : public wxTimer
541 _timer(wxeditor_uploaddialog* _dialog) { dialog = _dialog; start(); }
542 void start() { Start(500); }
543 void stop() { Stop(); }
544 void Notify()
546 dialog->timer_tick();
548 wxeditor_uploaddialog* dialog;
549 }* timer;
550 struct _games_output_handler : public http_request::output_handler {
551 ~_games_output_handler()
554 void header(const std::string& name, const std::string& cotent)
556 //No-op.
558 void write(const char* source, size_t srcsize)
560 std::string x(source, srcsize);
561 while(x.find_first_of("\n") < x.length()) {
562 size_t split = x.find_first_of("\n");
563 std::string line = x.substr(0, split);
564 x = x.substr(split + 1);
565 incomplete_line += line;
566 while(incomplete_line.length() > 0 &&
567 incomplete_line[incomplete_line.length() - 1] == '\r')
568 incomplete_line = incomplete_line.substr(0, incomplete_line.length() - 1);
569 choices.insert(incomplete_line);
570 incomplete_line = "";
572 if(x != "") incomplete_line += x;
574 void flush()
576 if(incomplete_line != "") choices.insert(incomplete_line);
578 std::string incomplete_line;
579 std::set<std::string> choices;
580 } games_output_handler;
581 wxTextCtrl* status;
582 wxTextCtrl* filename;
583 wxTextCtrl* title;
584 wxTextCtrl* description;
585 std::list<std::string> games_list;
586 wxStaticText* game;
587 wxButton* game_sel_button;
588 wxRadioButton* current;
589 wxRadioButton* file;
590 wxTextCtrl* ufilename;
591 wxButton* file_select;
592 wxGauge* progress;
593 wxCheckBox* hidden;
594 wxButton* ok;
595 wxButton* cancel;
596 file_upload* upload;
597 http_async_request* games_req;
598 upload_menu::upload_entry _entry;
601 wxeditor_uploaddialog::wxeditor_uploaddialog(wxWindow* parent, upload_menu::upload_entry entry)
602 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Upload file: " + entry.name), wxDefaultPosition,
603 wxSize(-1, -1))
605 _entry = entry;
606 upload = NULL;
607 Centre();
608 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
609 SetSizer(top_s);
611 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_uploaddialog::on_wclose), NULL, this);
613 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Filename:")), 0, wxGROW);
614 top_s->Add(filename = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, -1)), 0,
615 wxGROW);
616 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Title:")), 0, wxGROW);
617 top_s->Add(title = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, -1)), 0, wxGROW);
618 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Description:")), 0, wxGROW);
619 top_s->Add(description = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, 300),
620 wxTE_MULTILINE), 0, wxGROW);
621 wxBoxSizer* game_s = new wxBoxSizer(wxHORIZONTAL);
622 game_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW);
623 game_s->Add(game = new wxStaticText(this, wxID_ANY, wxT(NO_GAME_NAME)), 1, wxGROW);
624 game_s->Add(game_sel_button = new wxButton(this, wxID_ANY, wxT("Select")), 0, wxGROW);
625 top_s->Add(game_s, 0, wxGROW);
626 games_list.push_back(NO_GAME_NAME);
627 game_sel_button->Enable(false);
628 game_sel_button->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
629 wxCommandEventHandler(wxeditor_uploaddialog::on_game_sel), NULL, this);
630 top_s->Add(hidden = new wxCheckBox(this, wxID_ANY, wxT("Hidden")), 0, wxGROW);
632 top_s->Add(current = new wxRadioButton(this, wxID_ANY, wxT("Current movie"), wxDefaultPosition, wxDefaultSize,
633 wxRB_GROUP), 0, wxGROW);
634 top_s->Add(file = new wxRadioButton(this, wxID_ANY, wxT("Specified file:")), 0, wxGROW);
635 current->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
636 wxCommandEventHandler(wxeditor_uploaddialog::on_source_sel), NULL, this);
637 file->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
638 wxCommandEventHandler(wxeditor_uploaddialog::on_source_sel), NULL, this);
639 if(!lsnes_instance.mlogic || !our_rom.rtype || our_rom.rtype->isnull()) {
640 current->Enable(false);
641 file->SetValue(true);
644 wxBoxSizer* file_s = new wxBoxSizer(wxHORIZONTAL);
645 file_s->Add(ufilename = new wxTextCtrl(this, wxID_ANY, wxT("")), 1, wxGROW);
646 file_s->Add(file_select = new wxButton(this, wxID_ANY, wxT("...")), 0, wxGROW);
647 top_s->Add(file_s, 0, wxGROW);
648 file_select->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
649 wxCommandEventHandler(wxeditor_uploaddialog::on_file_sel), NULL, this);
650 ufilename->Enable(file->GetValue());
651 file_select->Enable(file->GetValue());
653 top_s->Add(status = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, 300),
654 wxTE_READONLY | wxTE_MULTILINE), 0, wxGROW);
655 top_s->Add(progress = new wxGauge(this, wxID_ANY, 1000000, wxDefaultPosition, wxSize(-1, 15),
656 wxGA_HORIZONTAL), 0, wxGROW);
658 status->AppendText(wxT("Obtaining list of games...\n"));
659 games_req = new http_async_request();
660 games_req->verb = "GET";
661 games_req->url = entry.url + "/games";
662 games_req->ihandler = NULL;
663 games_req->ohandler = &games_output_handler;
664 games_req->lauch_async();
666 timer = new _timer(this);
668 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
669 pbutton_s->AddStretchSpacer();
670 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("Upload")), 0, wxGROW);
671 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
672 wxCommandEventHandler(wxeditor_uploaddialog::on_ok), NULL, this);
673 pbutton_s->Add(cancel = new wxButton(this, wxID_OK, wxT("Cancel")), 0, wxGROW);
674 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
675 wxCommandEventHandler(wxeditor_uploaddialog::on_cancel), NULL, this);
676 top_s->Add(pbutton_s, 0, wxGROW);
677 top_s->SetSizeHints(this);
678 Fit();
681 void wxeditor_uploaddialog::timer_tick()
683 if(upload) {
684 for(auto i : upload->get_messages()) {
685 status->AppendText(towxstring(i + "\n"));
687 if(upload->finished) {
688 delete upload;
689 upload = NULL;
690 cancel->SetLabel(wxT("Close"));
691 } else {
692 auto prog = upload->get_progress_ppm();
693 if(prog < 0)
694 progress->Pulse();
695 else {
696 progress->SetRange(1000000);
697 progress->SetValue(prog);
700 } else if(games_req) {
701 progress->Pulse();
702 if(games_req->finished) {
703 std::string msg;
704 games_output_handler.flush();
705 if(games_req->errormsg != "") {
706 msg = (stringfmt() << "Error getting list of games: " << (games_req->errormsg)).str();
707 } else if(games_req->http_code != 200) {
708 msg = (stringfmt() << "Got unexpected HTTP status " << (games_req->http_code)).str();
709 } else {
710 for(auto i : games_output_handler.choices)
711 games_list.push_back(i);
712 if(games_list.size() > 1)
713 game_sel_button->Enable(true);
714 msg = "Got list of games.";
716 status->AppendText(towxstring(msg + "\n"));
717 delete games_req;
718 games_req = NULL;
719 wxCommandEvent e;
720 on_source_sel(e);
722 } else {
723 if(progress) {
724 progress->SetRange(1000000);
725 progress->SetValue(0);
730 void wxeditor_uploaddialog::on_ok(wxCommandEvent& e)
732 if(file->GetValue() && ufilename->GetValue().Length() == 0) return;
733 std::string fn = tostdstring(filename->GetValue());
734 std::vector<char> content;
735 if(file->GetValue()) {
736 if(fn == "") {
737 std::string name = tostdstring(ufilename->GetValue());
738 auto r = regex(".*/([^/]+)", name);
739 if(r) name = r[1];
740 filename->SetValue(towxstring(name));
742 boost::iostreams::back_insert_device<std::vector<char>> rd(content);
743 std::ifstream in(tostdstring(ufilename->GetValue()), std::ios::binary);
744 if(!in) {
745 status->AppendText(towxstring("Can't open '" + tostdstring(ufilename->GetValue()) + "'\n"));
746 return;
748 boost::iostreams::copy(in, rd);
749 } else {
750 if(fn.length() < 6 || fn.substr(fn.length() - 5) != ".lsmv")
751 filename->SetValue(towxstring(fn + ".lsmv"));
752 lsnes_instance.mlogic->get_mfile().is_savestate = false;
753 auto prj = lsnes_instance.project->get();
754 if(prj) {
755 lsnes_instance.mlogic->get_mfile().gamename = prj->gamename;
756 lsnes_instance.mlogic->get_mfile().authors = prj->authors;
758 lsnes_instance.mlogic->get_mfile().active_macros.clear();
759 std::ostringstream stream;
760 lsnes_instance.mlogic->get_mfile().save(stream, lsnes_instance.mlogic->get_rrdata());
761 std::string _stream = stream.str();
762 content = std::vector<char>(_stream.begin(), _stream.end());
764 ok->Enable(false);
765 upload = new file_upload();
766 upload->base_url = _entry.url;
767 upload->content = content;
768 upload->filename = tostdstring(filename->GetValue());
769 upload->title = tostdstring(title->GetValue());
770 upload->description = tostdstring(description->GetValue());
771 upload->gamename = tostdstring(game->GetLabel());
772 upload->hidden = hidden->GetValue();
773 upload->do_async();
776 void wxeditor_uploaddialog::on_source_sel(wxCommandEvent& e)
778 ufilename->Enable(file->GetValue());
779 file_select->Enable(file->GetValue());
780 if(!games_req) {
781 if(current->GetValue()) {
782 std::string curgame;
783 auto prj = lsnes_instance.project->get();
784 if(prj)
785 curgame = prj->gamename;
786 else
787 curgame = lsnes_instance.mlogic->get_mfile().gamename;
789 std::string plat = lookup_sysregion_mapping(
790 lsnes_instance.mlogic->get_mfile().gametype->get_name()) + " ";
791 size_t platlen = plat.length();
792 std::string c = tostdstring(game->GetLabel());
793 std::string fullname = plat + curgame;
794 //The rules here are:
795 //If there is fullname among games, select that.
796 //If not and the previous selection has the same system, keep it.
797 //Otherwise select (default).
798 bool done = false;
799 for(auto& i : games_list) {
800 if(i == fullname) {
801 game->SetLabel(fullname);
802 done = true;
803 break;
806 if(!done) {
807 if(c.substr(0, platlen) == plat)
808 done = true; //Keep.
810 if(!done)
811 game->SetLabel(NO_GAME_NAME);
816 void wxeditor_uploaddialog::on_file_sel(wxCommandEvent& e)
818 std::string f;
819 try {
820 f = pick_file(this, "Pick file to send", ".");
821 } catch(canceled_exception& e) {
822 return;
824 ufilename->SetValue(towxstring(f));
827 void wxeditor_uploaddialog::on_game_sel(wxCommandEvent& e)
829 auto pos = game_sel_button->GetScreenPosition();
830 std::string system;
831 if(current->GetValue())
832 system = lookup_sysregion_mapping(lsnes_instance.mlogic->get_mfile().gametype->get_name());
833 wxwin_gameselect* gs = new wxwin_gameselect(this, games_list, tostdstring(game->GetLabel()), system,
834 pos.x, pos.y);
835 if(gs->ShowModal() != wxID_OK) {
836 delete gs;
837 return;
839 game->SetLabel(towxstring(gs->get()));
840 return;
843 void wxeditor_uploaddialog::on_cancel(wxCommandEvent& e)
845 if(games_req) {
846 games_req->cancel();
847 while(!games_req->finished)
848 usleep(100000);
849 delete games_req;
851 if(upload) {
852 upload->cancel();
853 while(!upload->finished)
854 usleep(100000);
855 delete upload;
857 timer->stop();
858 delete timer;
859 EndModal(wxID_CANCEL);
862 void wxeditor_uploaddialog::on_wclose(wxCloseEvent& e)
864 wxCommandEvent e2;
865 on_cancel(e2);
870 upload_menu::upload_menu(wxWindow* win, int wxid_low, int wxid_high)
872 pwin = win;
873 wxid_range_low = wxid_low;
874 wxid_range_high = wxid_high;
875 Append(wxid_range_high, towxstring("Configure..."));
876 win->Connect(wxid_low, wxid_high, wxEVT_COMMAND_MENU_SELECTED,
877 wxCommandEventHandler(upload_menu::on_select), NULL, this);
879 std::ifstream in(get_config_path() + "/upload.cfg");
880 std::string line;
881 unsigned num = 0;
882 while(std::getline(in, line)) {
883 upload_entry entry;
884 try {
885 JSON::node n(line);
886 if(!n.field_exists("name") || n.type_of("name") != JSON::string)
887 continue;
888 if(!n.field_exists("url") || n.type_of("url") != JSON::string)
889 continue;
890 if(!n.field_exists("auth") || n.type_of("auth") != JSON::string)
891 continue;
892 entry.name = n["name"].as_string8();
893 entry.url = n["url"].as_string8();
894 std::string auth = n["auth"].as_string8();
895 if(auth == "dh25519")
896 entry.auth = upload_entry::AUTH_DH25519;
897 else
898 continue;
899 } catch(...) {
900 continue;
902 if(num == 0)
903 PrependSeparator();
904 entry.item = Prepend(wxid_range_low + num, towxstring(entry.name + "..."));
905 destinations[wxid_range_low + num] = entry;
906 num++;
911 upload_menu::~upload_menu()
915 void upload_menu::save()
917 std::string base = get_config_path() + "/upload.cfg";
919 std::ofstream out(base + ".tmp");
920 if(!out)
921 return;
922 for(auto i : destinations) {
923 upload_entry entry = i.second;
924 JSON::node n(JSON::object);
925 n["name"] = JSON::string(entry.name);
926 n["url"] = JSON::string(entry.url);
927 switch(entry.auth) {
928 case upload_entry::AUTH_DH25519:
929 n["auth"] = JSON::string("dh25519");
930 break;
932 out << n.serialize() << std::endl;
934 if(!out)
935 return;
937 directory::rename_overwrite((base + ".tmp").c_str(), base.c_str());
940 void upload_menu::configure_entry(unsigned num, struct upload_entry entry)
942 if(destinations.count(wxid_range_low + num)) {
943 //Reconfigure.
944 auto tmp = destinations[wxid_range_low + num].item;
945 destinations[wxid_range_low + num] = entry;
946 destinations[wxid_range_low + num].item = tmp;
947 destinations[wxid_range_low + num].item->SetItemLabel(towxstring(entry.name + "..."));
948 } else {
949 //New entry.
950 if(destinations.size() == 0)
951 PrependSeparator();
952 entry.item = Prepend(wxid_range_low + num, towxstring(entry.name + "..."));
953 destinations[wxid_range_low + num] = entry;
955 save();
958 std::set<unsigned> upload_menu::entries()
960 std::set<unsigned> r;
961 for(auto i : destinations)
962 r.insert(i.first - wxid_range_low);
963 return r;
966 upload_menu::upload_entry upload_menu::get_entry(unsigned num)
968 if(destinations.count(wxid_range_low + num))
969 return destinations[wxid_range_low + num];
970 else
971 throw std::runtime_error("No such upload target");
974 void upload_menu::delete_entry(unsigned num)
976 if(destinations.count(wxid_range_low + num)) {
977 Delete(destinations[wxid_range_low + num].item);
978 destinations.erase(wxid_range_low + num);
980 save();
983 void upload_menu::on_select(wxCommandEvent& e)
985 int id = e.GetId();
986 modal_pause_holder hld;
987 try {
988 wxDialog* f;
989 if(id == wxid_range_high) {
990 f = new wxeditor_uploadtargets(pwin, this);
991 } else if(destinations.count(id)) {
992 f = new wxeditor_uploaddialog(pwin, destinations[id]);
993 } else
994 return;
995 f->ShowModal();
996 f->Destroy();
997 } catch(canceled_exception& e) {
998 throw;
999 } catch(...) {
1000 throw canceled_exception();