Upload: Drop the system select, it isn't usable
[lsnes.git] / src / platform / wxwidgets / window-fileupload.cpp
blob872e66811a82e034b8c59ef760a20cbb7a90ce19
1 #include "platform/wxwidgets/platform.hpp"
2 #include "platform/wxwidgets/menu_upload.hpp"
3 #include "core/fileupload.hpp"
4 #include "core/misc.hpp"
5 #include "core/rom.hpp"
6 #include "core/project.hpp"
7 #include "core/moviedata.hpp"
8 #include "library/skein.hpp"
9 #include "library/zip.hpp"
10 #include "library/json.hpp"
11 #include "library/string.hpp"
12 #include <fstream>
13 #include <iostream>
14 #include <iomanip>
15 #include <wx/wx.h>
16 #include <wx/event.h>
17 #include <wx/control.h>
18 #include <wx/combobox.h>
19 #include <wx/spinctrl.h>
20 #include <boost/iostreams/categories.hpp>
21 #include <boost/iostreams/copy.hpp>
22 #include <boost/iostreams/stream.hpp>
23 #include <boost/iostreams/stream_buffer.hpp>
24 #include <boost/iostreams/filter/symmetric.hpp>
25 #include <boost/iostreams/filter/zlib.hpp>
26 #include <boost/iostreams/filtering_stream.hpp>
27 #include <boost/iostreams/device/back_inserter.hpp>
29 #define NO_GAME_NAME "(default)"
31 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir);
33 namespace
35 const std::string& str_tolower(const std::string& x)
37 static std::map<std::string, std::string> cache;
38 if(!cache.count(x)) {
39 std::ostringstream y;
40 for(auto i : x) {
41 if(i >= 'A' && i <= 'Z')
42 y << (char)(i + 32);
43 else
44 y << (char)i;
46 cache[x] = y.str();
48 return cache[x];
50 inline bool is_prefix(std::string a, std::string b)
52 return (a.length() <= b.length() && b.substr(0, a.length()) == a);
55 bool search_match(const std::string& term, const std::string& name)
57 std::set<std::string> searched;
58 std::vector<std::string> name_words;
59 for(auto i : token_iterator_foreach<char>(name, {" "})) if(i != "") name_words.push_back(str_tolower(i));
60 for(auto i : token_iterator_foreach<char>(term, {" "})) {
61 if(i == "")
62 continue;
63 std::string st = str_tolower(i);
64 if(searched.count(st))
65 continue;
66 for(size_t j = 0; j < name_words.size(); j++) {
67 if(is_prefix(st, name_words[j]))
68 goto out;
70 return false;
71 out:
72 searched.insert(st);
74 return true;
77 class wxwin_gameselect : public wxDialog
79 public:
80 wxwin_gameselect(wxWindow* parent, const std::list<std::string>& _choices, const std::string& dflt,
81 const std::string& system, int x, int y)
82 : wxDialog(parent, wxID_ANY, wxT("lsnes: Pick a game"), wxPoint(x, y)),
83 chosen(dflt), choices(_choices)
85 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
86 SetSizer(top_s);
88 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Search:")), 0, wxGROW);
89 top_s->Add(search = new wxTextCtrl(this, wxID_ANY, wxT("")), 1, wxGROW);
90 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW);
91 top_s->Add(games = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(400, 300)), 1, wxGROW);
93 search->Connect(wxEVT_COMMAND_TEXT_UPDATED,
94 wxCommandEventHandler(wxwin_gameselect::on_search_type), NULL, this);
95 games->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
96 wxCommandEventHandler(wxwin_gameselect::on_list_select), NULL, this);
98 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
99 pbutton_s->AddStretchSpacer();
100 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
101 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
102 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
103 wxCommandEventHandler(wxwin_gameselect::on_ok), NULL, this);
104 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
105 wxCommandEventHandler(wxwin_gameselect::on_cancel), NULL, this);
106 top_s->Add(pbutton_s, 0, wxGROW);
107 wxBoxSizer* button_s = new wxBoxSizer(wxHORIZONTAL);
109 auto dsplit = split_name(dflt);
110 bool wrong_default = (dflt != NO_GAME_NAME && system != "" && dsplit.first != system);
111 if(system != "" && !wrong_default) {
112 //Populate just one system.
113 rsystem = system;
114 oneplat = true;
115 } else {
116 oneplat = false;
118 wxCommandEvent e;
119 on_search_type(e);
120 games->SetStringSelection(dflt);
122 std::string get()
124 return chosen;
126 void on_search_type(wxCommandEvent& e)
128 std::string current;
129 if(games->GetSelection() != wxNOT_FOUND)
130 current = games->GetStringSelection();
131 games->Clear();
132 std::string terms = tostdstring(search->GetValue());
133 for(auto& i : choices) {
134 auto g = split_name(i);
135 if(i != current && i != NO_GAME_NAME) {
136 if(oneplat && g.first != rsystem)
137 continue; //Wrong system.
138 if(!search_match(terms, i))
139 continue; //Doesn't match terms.
141 games->Append(towxstring(i));
143 if(current != "")
144 games->SetStringSelection(current);
146 void on_list_select(wxCommandEvent& e)
148 if(games->GetSelection() != wxNOT_FOUND) {
149 chosen = tostdstring(games->GetStringSelection());
150 ok->Enable(true);
151 } else {
152 ok->Enable(false);
155 void on_ok(wxCommandEvent& e)
157 EndModal(wxID_OK);
159 void on_cancel(wxCommandEvent& e)
161 EndModal(wxID_CANCEL);
163 private:
164 std::pair<std::string, std::string> split_name(const std::string& name)
166 if(name == NO_GAME_NAME)
167 return std::make_pair("N/A", NO_GAME_NAME);
168 std::string _name = name;
169 size_t r = _name.find_first_of(" ");
170 if(r >= _name.length())
171 return std::make_pair("???", name);
172 else if(r == 0)
173 return std::make_pair("???", _name.substr(1));
174 else
175 return std::make_pair(_name.substr(0, r), _name.substr(r + 1));
177 std::string chosen;
178 const std::list<std::string>& choices;
179 wxTextCtrl* search;
180 wxListBox* games;
181 wxButton* ok;
182 wxButton* cancel;
183 std::string old_system;
184 bool oneplat;
185 std::string rsystem;
188 class wxeditor_uploadtarget : public wxDialog
190 public:
191 wxeditor_uploadtarget(wxWindow* parent);
192 wxeditor_uploadtarget(wxWindow* parent, upload_menu::upload_entry entry);
193 upload_menu::upload_entry get_entry();
194 void generate_dh25519(wxCommandEvent& e);
195 void on_ok(wxCommandEvent& e);
196 void on_cancel(wxCommandEvent& e);
197 void on_auth_sel(wxCommandEvent& e);
198 void revalidate(wxCommandEvent& e);
199 private:
200 void dh25519_fill_box();
201 void ctor_common();
202 wxButton* ok;
203 wxButton* cancel;
204 wxTextCtrl* name;
205 wxTextCtrl* url;
206 wxComboBox* auth;
207 wxPanel* dh25519_p;
208 wxTextCtrl* dh25519_k;
209 wxButton* dh25519_g;
212 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow* parent)
213 : wxDialog(parent, wxID_ANY, towxstring("lsnes: New upload target"))
215 ctor_common();
216 wxCommandEvent e;
217 on_auth_sel(e);
220 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow* parent, upload_menu::upload_entry entry)
221 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Edit upload target: " + entry.name))
223 ctor_common();
224 name->SetValue(towxstring(entry.name));
225 url->SetValue(towxstring(entry.url));
226 switch(entry.auth) {
227 case upload_menu::upload_entry::AUTH_DH25519:
228 auth->SetSelection(0);
229 wxCommandEvent e;
230 on_auth_sel(e);
231 break;
235 void wxeditor_uploadtarget::dh25519_fill_box()
237 try {
238 uint8_t rbuf[32];
239 get_dh25519_pubkey(rbuf);
240 char out[65];
241 out[64] = 0;
242 for(unsigned i = 0; i < 32; i++)
243 sprintf(out + 2 * i, "%02x", rbuf[i]);
244 dh25519_k->SetValue(towxstring(out));
245 dh25519_g->Disable();
246 } catch(...) {
247 dh25519_k->SetValue(towxstring("(Not available)"));
248 dh25519_g->Enable();
250 wxCommandEvent e;
251 revalidate(e);
254 void wxeditor_uploadtarget::ctor_common()
256 ok = NULL;
257 std::vector<wxString> auth_choices;
258 Center();
259 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
260 SetSizer(top_s);
262 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring("Name")), 0, wxGROW);
263 top_s->Add(name = new wxTextCtrl(this, wxID_ANY, towxstring(""), wxDefaultPosition, wxSize(550, -1)), 0,
264 wxGROW);
265 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring("URL")), 0, wxGROW);
266 top_s->Add(url = new wxTextCtrl(this, wxID_ANY, towxstring(""), wxDefaultPosition, wxSize(550, -1)), 0,
267 wxGROW);
268 name->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_uploadtarget::revalidate), NULL,
269 this);
270 url->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_uploadtarget::revalidate), NULL,
271 this);
273 top_s->Add(new wxStaticText(this, wxID_ANY, towxstring("Authentication")), 0, wxGROW);
274 auth_choices.push_back(towxstring("dh25519"));
275 top_s->Add(auth = new wxComboBox(this, wxID_ANY, auth_choices[0], wxDefaultPosition, wxDefaultSize,
276 auth_choices.size(), &auth_choices[0], wxCB_READONLY), 0, wxGROW);
278 dh25519_p = new wxPanel(this, wxID_ANY);
279 wxBoxSizer* dh25519_s = new wxBoxSizer(wxVERTICAL);
280 dh25519_p->SetSizer(dh25519_s);
281 wxStaticBox* dh25519_b = new wxStaticBox(dh25519_p, wxID_ANY, towxstring("Authentication parameters"));
282 wxStaticBoxSizer* dh25519_s2 = new wxStaticBoxSizer(dh25519_b, wxVERTICAL);
283 top_s->Add(dh25519_p, 0, wxGROW);
284 dh25519_s->Add(dh25519_s2, 0, wxGROW);
285 dh25519_s2->Add(new wxStaticText(dh25519_p, wxID_ANY, towxstring("Key")), 0, wxGROW);
286 dh25519_s2->Add(dh25519_k = new wxTextCtrl(dh25519_p, wxID_ANY, towxstring(""), wxDefaultPosition,
287 wxSize(550, -1), wxTE_READONLY), 0, wxGROW);
288 dh25519_s2->Add(dh25519_g = new wxButton(dh25519_p, wxID_ANY, towxstring("Generate")), 0, wxGROW);
289 dh25519_s->SetSizeHints(dh25519_p);
290 dh25519_fill_box();
292 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
293 pbutton_s->AddStretchSpacer();
294 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
295 pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW);
296 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
297 wxCommandEventHandler(wxeditor_uploadtarget::on_ok), NULL, this);
298 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
299 wxCommandEventHandler(wxeditor_uploadtarget::on_cancel), NULL, this);
300 top_s->Add(pbutton_s, 0, wxGROW);
302 wxCommandEvent e;
303 revalidate(e);
305 top_s->SetSizeHints(this);
306 Fit();
309 void wxeditor_uploadtarget::on_ok(wxCommandEvent& e)
311 EndModal(wxID_OK);
314 void wxeditor_uploadtarget::on_cancel(wxCommandEvent& e)
316 EndModal(wxID_CANCEL);
319 void wxeditor_uploadtarget::generate_dh25519(wxCommandEvent& e)
321 try {
322 std::string entropy = pick_text(this, "Enter garbage", "Mash some garbage from keyboard to derive\n"
323 "key from:", "", true);
324 uint8_t rbuf[192];
325 highrandom_256(rbuf + 0);
326 highrandom_256(rbuf + 32);
327 std::vector<char> x;
328 x.resize(entropy.length());
329 std::copy(entropy.begin(), entropy.end(), x.begin());
330 skein::hash h(skein::hash::PIPE_1024, 1024);
331 h.write((uint8_t*)&x[0], x.size());
332 h.read((uint8_t*)rbuf + 64);
334 std::ofstream fp(get_config_path() + "/dh25519.key", std::ios::binary);
335 if(!fp) throw std::runtime_error("Can't open keyfile");
336 #if !defined(_WIN32) && !defined(_WIN64)
337 chmod((get_config_path() + "/dh25519.key").c_str(), 0600);
338 #endif
339 fp.write((char*)rbuf, 192);
340 if(!fp) throw std::runtime_error("Can't write keyfile");
342 dh25519_fill_box();
343 } catch(canceled_exception& e) {
344 return;
345 } catch(std::exception& e) {
346 show_message_ok(this, "Generate keys error", std::string("Error generating keys:") + e.what(),
347 wxICON_EXCLAMATION);
348 return;
352 void wxeditor_uploadtarget::on_auth_sel(wxCommandEvent& e)
354 dh25519_p->Show(false);
355 switch(auth->GetSelection()) {
356 case 0:
357 dh25519_p->Show(true);
358 break;
360 revalidate(e);
361 Fit();
364 upload_menu::upload_entry wxeditor_uploadtarget::get_entry()
366 upload_menu::upload_entry ent;
367 ent.name = tostdstring(name->GetValue());
368 ent.url = tostdstring(url->GetValue());
369 ent.auth = upload_menu::upload_entry::AUTH_DH25519;
370 switch(auth->GetSelection()) {
371 case 0:
372 ent.auth = upload_menu::upload_entry::AUTH_DH25519;
373 break;
375 return ent;
378 void wxeditor_uploadtarget::revalidate(wxCommandEvent& e)
380 bool valid = true;
381 if(!name || (name->GetValue().Length() == 0)) valid = false;
382 std::string URL = url ? tostdstring(url->GetValue()) : "";
383 if(!regex_match("https?://(([!$&'()*+,;=:A-Za-z0-9._~-]|%[0-9A-Fa-f][0-9A-Fa-f])+|\\[v[0-9A-Fa-f]\\."
384 "([!$&'()*+,;=:A-Za-z0-9._~-]|%[0-9A-Fa-f][0-9A-Fa-f])+\\]|\\[[0-9A-Fa-f:]+\\])"
385 "(/([!$&'()*+,;=:A-Za-z0-9._~@-]|%[0-9A-Fa-f][0-9A-Fa-f])+)*", URL)) valid = false;
386 if(!auth || (auth->GetSelection() == 0 && dh25519_g->IsEnabled())) valid = false;
387 if(ok) ok->Enable(valid);
390 class wxeditor_uploadtargets : public wxDialog
392 public:
393 wxeditor_uploadtargets(wxWindow* parent, upload_menu* menu);
394 void on_ok(wxCommandEvent& e);
395 void on_add(wxCommandEvent& e);
396 void on_modify(wxCommandEvent& e);
397 void on_remove(wxCommandEvent& e);
398 void on_list_sel(wxCommandEvent& e);
399 private:
400 void refresh();
401 upload_menu* umenu;
402 std::map<int, unsigned> id_map;
403 wxListBox* list;
404 wxButton* ok;
405 wxButton* add;
406 wxButton* modify;
407 wxButton* _delete;
410 wxeditor_uploadtargets::wxeditor_uploadtargets(wxWindow* parent, upload_menu* menu)
411 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Configure upload targets"), wxDefaultPosition,
412 wxSize(400, 500))
414 umenu = menu;
415 Center();
416 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
417 SetSizer(top_s);
419 top_s->Add(list = new wxListBox(this, wxID_ANY), 1, wxGROW);
420 list->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
421 wxCommandEventHandler(wxeditor_uploadtargets::on_list_sel), NULL, this);
423 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
424 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
425 pbutton_s->AddStretchSpacer();
426 pbutton_s->Add(add = new wxButton(this, wxID_ADD, wxT("Add")), 0, wxGROW);
427 pbutton_s->Add(modify = new wxButton(this, wxID_EDIT, wxT("Modify")), 0, wxGROW);
428 pbutton_s->Add(_delete = new wxButton(this, wxID_DELETE, wxT("Delete")), 0, wxGROW);
429 modify->Enable(false);
430 _delete->Enable(false);
431 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
432 wxCommandEventHandler(wxeditor_uploadtargets::on_ok), NULL, this);
433 add->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
434 wxCommandEventHandler(wxeditor_uploadtargets::on_add), NULL, this);
435 modify->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
436 wxCommandEventHandler(wxeditor_uploadtargets::on_modify), NULL, this);
437 _delete->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
438 wxCommandEventHandler(wxeditor_uploadtargets::on_remove), NULL, this);
439 top_s->Add(pbutton_s, 0, wxGROW);
441 refresh();
442 top_s->SetSizeHints(this);
443 Fit();
446 void wxeditor_uploadtargets::on_ok(wxCommandEvent& e)
448 EndModal(wxID_OK);
451 void wxeditor_uploadtargets::on_add(wxCommandEvent& e)
453 auto f = new wxeditor_uploadtarget(this);
454 int r = f->ShowModal();
455 if(r == wxID_OK) {
456 unsigned ent = 0;
457 auto used = umenu->entries();
458 while(used.count(ent)) ent++;
459 umenu->configure_entry(ent, f->get_entry());
461 f->Destroy();
462 refresh();
465 void wxeditor_uploadtargets::on_modify(wxCommandEvent& e)
467 auto s = list->GetSelection();
468 if(s == wxNOT_FOUND) return;
469 if(!id_map.count(s)) return;
470 auto f = new wxeditor_uploadtarget(this, umenu->get_entry(id_map[s]));
471 int r = f->ShowModal();
472 if(r == wxID_OK)
473 umenu->configure_entry(id_map[s], f->get_entry());
474 f->Destroy();
475 refresh();
478 void wxeditor_uploadtargets::on_remove(wxCommandEvent& e)
480 auto s = list->GetSelection();
481 if(s == wxNOT_FOUND) return;
482 if(!id_map.count(s)) return;
483 unsigned id = id_map[s];
484 umenu->delete_entry(id);
485 refresh();
488 void wxeditor_uploadtargets::on_list_sel(wxCommandEvent& e)
490 auto s = list->GetSelection();
491 modify->Enable(s != wxNOT_FOUND);
492 _delete->Enable(s != wxNOT_FOUND);
495 void wxeditor_uploadtargets::refresh()
497 auto ents = umenu->entries();
498 auto sel = list->GetSelection();
499 auto sel_id = id_map.count(sel) ? id_map[sel] : 0xFFFFFFFFU;
501 list->Clear();
502 id_map.clear();
503 int num = 0;
504 for(auto i : ents) {
505 auto ent = umenu->get_entry(i);
506 list->Append(towxstring(ent.name));
507 id_map[num++] = i;
510 //Try to keep selection.
511 if(sel_id != 0xFFFFFFFFU) {
512 int x = wxNOT_FOUND;
513 for(auto i : id_map)
514 if(i.second == sel_id)
515 x = i.first;
516 if(x != wxNOT_FOUND)
517 list->SetSelection(x);
518 } else if(sel < list->GetCount())
519 list->SetSelection(sel);
522 class wxeditor_uploaddialog : public wxDialog
524 public:
525 wxeditor_uploaddialog(wxWindow* parent, upload_menu::upload_entry entry);
526 void on_ok(wxCommandEvent& e);
527 void on_cancel(wxCommandEvent& e);
528 void on_source_sel(wxCommandEvent& e);
529 void on_file_sel(wxCommandEvent& e);
530 void on_wclose(wxCloseEvent& e);
531 void timer_tick();
532 void on_game_sel(wxCommandEvent& e);
533 private:
534 struct _timer : public wxTimer
536 _timer(wxeditor_uploaddialog* _dialog) { dialog = _dialog; start(); }
537 void start() { Start(500); }
538 void stop() { Stop(); }
539 void Notify()
541 dialog->timer_tick();
543 wxeditor_uploaddialog* dialog;
544 }* timer;
545 struct _games_output_handler : public http_request::output_handler {
546 ~_games_output_handler()
549 void header(const std::string& name, const std::string& cotent)
551 //No-op.
553 void write(const char* source, size_t srcsize)
555 std::string x(source, srcsize);
556 while(x.find_first_of("\n") < x.length()) {
557 size_t split = x.find_first_of("\n");
558 std::string line = x.substr(0, split);
559 x = x.substr(split + 1);
560 incomplete_line += line;
561 while(incomplete_line.length() > 0 &&
562 incomplete_line[incomplete_line.length() - 1] == '\r')
563 incomplete_line = incomplete_line.substr(0, incomplete_line.length() - 1);
564 choices.insert(incomplete_line);
565 incomplete_line = "";
567 if(x != "") incomplete_line += x;
569 void flush()
571 if(incomplete_line != "") choices.insert(incomplete_line);
573 std::string incomplete_line;
574 std::set<std::string> choices;
575 } games_output_handler;
576 wxTextCtrl* status;
577 wxTextCtrl* filename;
578 wxTextCtrl* title;
579 wxTextCtrl* description;
580 std::list<std::string> games_list;
581 wxStaticText* game;
582 wxButton* game_sel_button;
583 wxRadioButton* current;
584 wxRadioButton* file;
585 wxTextCtrl* ufilename;
586 wxButton* file_select;
587 wxGauge* progress;
588 wxCheckBox* hidden;
589 wxButton* ok;
590 wxButton* cancel;
591 file_upload* upload;
592 http_async_request* games_req;
593 upload_menu::upload_entry _entry;
596 wxeditor_uploaddialog::wxeditor_uploaddialog(wxWindow* parent, upload_menu::upload_entry entry)
597 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Upload file: " + entry.name), wxDefaultPosition,
598 wxSize(-1, -1))
600 _entry = entry;
601 upload = NULL;
602 Centre();
603 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
604 SetSizer(top_s);
606 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_uploaddialog::on_wclose), NULL, this);
608 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Filename:")), 0, wxGROW);
609 top_s->Add(filename = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, -1)), 0,
610 wxGROW);
611 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Title:")), 0, wxGROW);
612 top_s->Add(title = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, -1)), 0, wxGROW);
613 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Description:")), 0, wxGROW);
614 top_s->Add(description = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, 300),
615 wxTE_MULTILINE), 0, wxGROW);
616 wxBoxSizer* game_s = new wxBoxSizer(wxHORIZONTAL);
617 game_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW);
618 game_s->Add(game = new wxStaticText(this, wxID_ANY, wxT(NO_GAME_NAME)), 1, wxGROW);
619 game_s->Add(game_sel_button = new wxButton(this, wxID_ANY, wxT("Select")), 0, wxGROW);
620 top_s->Add(game_s, 0, wxGROW);
621 games_list.push_back(NO_GAME_NAME);
622 game_sel_button->Enable(false);
623 game_sel_button->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
624 wxCommandEventHandler(wxeditor_uploaddialog::on_game_sel), NULL, this);
625 top_s->Add(hidden = new wxCheckBox(this, wxID_ANY, wxT("Hidden")), 0, wxGROW);
627 top_s->Add(current = new wxRadioButton(this, wxID_ANY, wxT("Current movie"), wxDefaultPosition, wxDefaultSize,
628 wxRB_GROUP), 0, wxGROW);
629 top_s->Add(file = new wxRadioButton(this, wxID_ANY, wxT("Specified file:")), 0, wxGROW);
630 current->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
631 wxCommandEventHandler(wxeditor_uploaddialog::on_source_sel), NULL, this);
632 file->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED,
633 wxCommandEventHandler(wxeditor_uploaddialog::on_source_sel), NULL, this);
634 if(!movb || !our_rom.rtype || our_rom.rtype->isnull()) {
635 current->Enable(false);
636 file->SetValue(true);
639 wxBoxSizer* file_s = new wxBoxSizer(wxHORIZONTAL);
640 file_s->Add(ufilename = new wxTextCtrl(this, wxID_ANY, wxT("")), 1, wxGROW);
641 file_s->Add(file_select = new wxButton(this, wxID_ANY, wxT("...")), 0, wxGROW);
642 top_s->Add(file_s, 0, wxGROW);
643 file_select->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
644 wxCommandEventHandler(wxeditor_uploaddialog::on_file_sel), NULL, this);
645 ufilename->Enable(file->GetValue());
646 file_select->Enable(file->GetValue());
648 top_s->Add(status = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(550, 300),
649 wxTE_READONLY | wxTE_MULTILINE), 0, wxGROW);
650 top_s->Add(progress = new wxGauge(this, wxID_ANY, 1000000, wxDefaultPosition, wxSize(-1, 15),
651 wxGA_HORIZONTAL), 0, wxGROW);
653 status->AppendText(wxT("Obtaining list of games...\n"));
654 games_req = new http_async_request();
655 games_req->verb = "GET";
656 games_req->url = entry.url + "/games";
657 games_req->ihandler = NULL;
658 games_req->ohandler = &games_output_handler;
659 games_req->lauch_async();
661 timer = new _timer(this);
663 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
664 pbutton_s->AddStretchSpacer();
665 pbutton_s->Add(ok = new wxButton(this, wxID_OK, wxT("Upload")), 0, wxGROW);
666 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
667 wxCommandEventHandler(wxeditor_uploaddialog::on_ok), NULL, this);
668 pbutton_s->Add(cancel = new wxButton(this, wxID_OK, wxT("Cancel")), 0, wxGROW);
669 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
670 wxCommandEventHandler(wxeditor_uploaddialog::on_cancel), NULL, this);
671 top_s->Add(pbutton_s, 0, wxGROW);
672 top_s->SetSizeHints(this);
673 Fit();
676 void wxeditor_uploaddialog::timer_tick()
678 if(upload) {
679 for(auto i : upload->get_messages()) {
680 status->AppendText(towxstring(i + "\n"));
682 if(upload->finished) {
683 delete upload;
684 upload = NULL;
685 cancel->SetLabel(wxT("Close"));
686 } else {
687 auto prog = upload->get_progress_ppm();
688 if(prog < 0)
689 progress->Pulse();
690 else {
691 progress->SetRange(1000000);
692 progress->SetValue(prog);
695 } else if(games_req) {
696 progress->Pulse();
697 if(games_req->finished) {
698 std::string msg;
699 games_output_handler.flush();
700 if(games_req->errormsg != "") {
701 msg = (stringfmt() << "Error getting list of games: " << (games_req->errormsg)).str();
702 } else if(games_req->http_code != 200) {
703 msg = (stringfmt() << "Got unexpected HTTP status " << (games_req->http_code)).str();
704 } else {
705 for(auto i : games_output_handler.choices)
706 games_list.push_back(i);
707 if(games_list.size() > 1)
708 game_sel_button->Enable(true);
709 msg = "Got list of games.";
711 status->AppendText(towxstring(msg + "\n"));
712 delete games_req;
713 games_req = NULL;
714 wxCommandEvent e;
715 on_source_sel(e);
717 } else {
718 if(progress) {
719 progress->SetRange(1000000);
720 progress->SetValue(0);
725 void wxeditor_uploaddialog::on_ok(wxCommandEvent& e)
727 if(file->GetValue() && ufilename->GetValue().Length() == 0) return;
728 std::string fn = tostdstring(filename->GetValue());
729 std::vector<char> content;
730 if(file->GetValue()) {
731 if(fn == "") {
732 std::string name = tostdstring(ufilename->GetValue());
733 auto r = regex(".*/([^/]+)", name);
734 if(r) name = r[1];
735 filename->SetValue(towxstring(name));
737 boost::iostreams::back_insert_device<std::vector<char>> rd(content);
738 std::ifstream in(tostdstring(ufilename->GetValue()), std::ios::binary);
739 if(!in) {
740 status->AppendText(towxstring("Can't open '" + tostdstring(ufilename->GetValue()) + "'\n"));
741 return;
743 boost::iostreams::copy(in, rd);
744 } else {
745 if(fn.length() < 6 || fn.substr(fn.length() - 5) != ".lsmv")
746 filename->SetValue(towxstring(fn + ".lsmv"));
747 movb.get_mfile().is_savestate = false;
748 auto prj = project_get();
749 if(prj) {
750 movb.get_mfile().gamename = prj->gamename;
751 movb.get_mfile().authors = prj->authors;
753 movb.get_mfile().active_macros.clear();
754 std::ostringstream stream;
755 movb.get_mfile().save(stream, movb.get_rrdata());
756 std::string _stream = stream.str();
757 content = std::vector<char>(_stream.begin(), _stream.end());
759 ok->Enable(false);
760 upload = new file_upload();
761 upload->base_url = _entry.url;
762 upload->content = content;
763 upload->filename = tostdstring(filename->GetValue());
764 upload->title = tostdstring(title->GetValue());
765 upload->description = tostdstring(description->GetValue());
766 upload->gamename = tostdstring(game->GetLabel());
767 upload->hidden = hidden->GetValue();
768 upload->do_async();
771 void wxeditor_uploaddialog::on_source_sel(wxCommandEvent& e)
773 ufilename->Enable(file->GetValue());
774 file_select->Enable(file->GetValue());
775 if(!games_req) {
776 if(current->GetValue()) {
777 std::string curgame;
778 auto prj = project_get();
779 if(prj)
780 curgame = prj->gamename;
781 else
782 curgame = movb.get_mfile().gamename;
784 std::string plat = lookup_sysregion_mapping(movb.get_mfile().gametype->get_name()) + " ";
785 size_t platlen = plat.length();
786 std::string c = tostdstring(game->GetLabel());
787 std::string fullname = plat + curgame;
788 //The rules here are:
789 //If there is fullname among games, select that.
790 //If not and the previous selection has the same system, keep it.
791 //Otherwise select (default).
792 bool done = false;
793 for(auto& i : games_list) {
794 if(i == fullname) {
795 game->SetLabel(fullname);
796 done = true;
797 break;
800 if(!done) {
801 if(c.substr(0, platlen) == plat)
802 done = true; //Keep.
804 if(!done)
805 game->SetLabel(NO_GAME_NAME);
810 void wxeditor_uploaddialog::on_file_sel(wxCommandEvent& e)
812 std::string f;
813 try {
814 f = pick_file(this, "Pick file to send", ".");
815 } catch(canceled_exception& e) {
816 return;
818 ufilename->SetValue(towxstring(f));
821 void wxeditor_uploaddialog::on_game_sel(wxCommandEvent& e)
823 auto pos = game_sel_button->GetScreenPosition();
824 std::string system;
825 if(current->GetValue())
826 system = lookup_sysregion_mapping(movb.get_mfile().gametype->get_name());
827 wxwin_gameselect* gs = new wxwin_gameselect(this, games_list, tostdstring(game->GetLabel()), system,
828 pos.x, pos.y);
829 if(gs->ShowModal() != wxID_OK) {
830 delete gs;
831 return;
833 game->SetLabel(towxstring(gs->get()));
834 return;
837 void wxeditor_uploaddialog::on_cancel(wxCommandEvent& e)
839 if(games_req) {
840 games_req->cancel();
841 while(!games_req->finished)
842 usleep(100000);
843 delete games_req;
845 if(upload) {
846 upload->cancel();
847 while(!upload->finished)
848 usleep(100000);
849 delete upload;
851 timer->stop();
852 delete timer;
853 EndModal(wxID_CANCEL);
856 void wxeditor_uploaddialog::on_wclose(wxCloseEvent& e)
858 wxCommandEvent e2;
859 on_cancel(e2);
864 upload_menu::upload_menu(wxWindow* win, int wxid_low, int wxid_high)
866 pwin = win;
867 wxid_range_low = wxid_low;
868 wxid_range_high = wxid_high;
869 Append(wxid_range_high, towxstring("Configure..."));
870 win->Connect(wxid_low, wxid_high, wxEVT_COMMAND_MENU_SELECTED,
871 wxCommandEventHandler(upload_menu::on_select), NULL, this);
873 std::ifstream in(get_config_path() + "/upload.cfg");
874 std::string line;
875 unsigned num = 0;
876 while(std::getline(in, line)) {
877 upload_entry entry;
878 try {
879 JSON::node n(line);
880 if(!n.field_exists("name") || n.type_of("name") != JSON::string)
881 continue;
882 if(!n.field_exists("url") || n.type_of("url") != JSON::string)
883 continue;
884 if(!n.field_exists("auth") || n.type_of("auth") != JSON::string)
885 continue;
886 entry.name = n["name"].as_string8();
887 entry.url = n["url"].as_string8();
888 std::string auth = n["auth"].as_string8();
889 if(auth == "dh25519")
890 entry.auth = upload_entry::AUTH_DH25519;
891 else
892 continue;
893 } catch(...) {
894 continue;
896 if(num == 0)
897 PrependSeparator();
898 entry.item = Prepend(wxid_range_low + num, towxstring(entry.name + "..."));
899 destinations[wxid_range_low + num] = entry;
900 num++;
905 upload_menu::~upload_menu()
909 void upload_menu::save()
911 std::string base = get_config_path() + "/upload.cfg";
913 std::ofstream out(base + ".tmp");
914 if(!out)
915 return;
916 for(auto i : destinations) {
917 upload_entry entry = i.second;
918 JSON::node n(JSON::object);
919 n["name"] = JSON::string(entry.name);
920 n["url"] = JSON::string(entry.url);
921 switch(entry.auth) {
922 case upload_entry::AUTH_DH25519:
923 n["auth"] = JSON::string("dh25519");
924 break;
926 out << n.serialize() << std::endl;
928 if(!out)
929 return;
931 zip::rename_overwrite((base + ".tmp").c_str(), base.c_str());
934 void upload_menu::configure_entry(unsigned num, struct upload_entry entry)
936 if(destinations.count(wxid_range_low + num)) {
937 //Reconfigure.
938 auto tmp = destinations[wxid_range_low + num].item;
939 destinations[wxid_range_low + num] = entry;
940 destinations[wxid_range_low + num].item = tmp;
941 destinations[wxid_range_low + num].item->SetItemLabel(towxstring(entry.name + "..."));
942 } else {
943 //New entry.
944 if(destinations.size() == 0)
945 PrependSeparator();
946 entry.item = Prepend(wxid_range_low + num, towxstring(entry.name + "..."));
947 destinations[wxid_range_low + num] = entry;
949 save();
952 std::set<unsigned> upload_menu::entries()
954 std::set<unsigned> r;
955 for(auto i : destinations)
956 r.insert(i.first - wxid_range_low);
957 return r;
960 upload_menu::upload_entry upload_menu::get_entry(unsigned num)
962 if(destinations.count(wxid_range_low + num))
963 return destinations[wxid_range_low + num];
964 else
965 throw std::runtime_error("No such upload target");
968 void upload_menu::delete_entry(unsigned num)
970 if(destinations.count(wxid_range_low + num)) {
971 Delete(destinations[wxid_range_low + num].item);
972 destinations.erase(wxid_range_low + num);
974 save();
977 void upload_menu::on_select(wxCommandEvent& e)
979 int id = e.GetId();
980 modal_pause_holder hld;
981 try {
982 wxDialog* f;
983 if(id == wxid_range_high) {
984 f = new wxeditor_uploadtargets(pwin, this);
985 } else if(destinations.count(id)) {
986 f = new wxeditor_uploaddialog(pwin, destinations[id]);
987 } else
988 return;
989 f->ShowModal();
990 f->Destroy();
991 } catch(canceled_exception& e) {
992 throw;
993 } catch(...) {
994 throw canceled_exception();