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"
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
);
38 const std::string
& str_tolower(const std::string
& x
)
40 static std::map
<std::string
, std::string
> cache
;
44 if(i
>= 'A' && i
<= 'Z')
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
, {" "})) {
66 std::string st
= str_tolower(i
);
67 if(searched
.count(st
))
69 for(size_t j
= 0; j
< name_words
.size(); j
++) {
70 if(is_prefix(st
, name_words
[j
]))
80 class wxwin_gameselect
: public wxDialog
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
);
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.
122 games
->SetStringSelection(dflt
);
128 void on_search_type(wxCommandEvent
& e
)
131 if(games
->GetSelection() != wxNOT_FOUND
)
132 current
= games
->GetStringSelection();
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
));
146 games
->SetStringSelection(current
);
148 void on_list_select(wxCommandEvent
& e
)
150 if(games
->GetSelection() != wxNOT_FOUND
) {
151 chosen
= tostdstring(games
->GetStringSelection());
157 void on_ok(wxCommandEvent
& e
)
161 void on_cancel(wxCommandEvent
& e
)
163 EndModal(wxID_CANCEL
);
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
);
175 return std::make_pair("???", _name
.substr(1));
177 return std::make_pair(_name
.substr(0, r
), _name
.substr(r
+ 1));
180 const std::list
<std::string
>& choices
;
185 std::string old_system
;
190 class wxeditor_uploadtarget
: public wxDialog
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
);
202 void dh25519_fill_box();
210 wxTextCtrl
* dh25519_k
;
214 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow
* parent
)
215 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: New upload target"))
222 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow
* parent
, upload_menu::upload_entry entry
)
223 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Edit upload target: " + entry
.name
))
226 name
->SetValue(towxstring(entry
.name
));
227 url
->SetValue(towxstring(entry
.url
));
229 case upload_menu::upload_entry::AUTH_DH25519
:
230 auth
->SetSelection(0);
237 void wxeditor_uploadtarget::dh25519_fill_box()
241 get_dh25519_pubkey(rbuf
);
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();
249 dh25519_k
->SetValue(towxstring("(Not available)"));
256 void wxeditor_uploadtarget::ctor_common()
259 std::vector
<wxString
> auth_choices
;
261 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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,
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,
270 name
->Connect(wxEVT_COMMAND_TEXT_UPDATED
, wxCommandEventHandler(wxeditor_uploadtarget::revalidate
), NULL
,
272 url
->Connect(wxEVT_COMMAND_TEXT_UPDATED
, wxCommandEventHandler(wxeditor_uploadtarget::revalidate
), NULL
,
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
);
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
);
307 top_s
->SetSizeHints(this);
311 void wxeditor_uploadtarget::on_ok(wxCommandEvent
& e
)
316 void wxeditor_uploadtarget::on_cancel(wxCommandEvent
& e
)
318 EndModal(wxID_CANCEL
);
321 void wxeditor_uploadtarget::generate_dh25519(wxCommandEvent
& e
)
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);
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);
341 fp
.write((char*)rbuf
, 192);
342 if(!fp
) throw std::runtime_error("Can't write keyfile");
344 skein::zeroize(rbuf
, sizeof(rbuf
));
346 } catch(canceled_exception
& e
) {
347 skein::zeroize(rbuf
, sizeof(rbuf
));
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(),
357 void wxeditor_uploadtarget::on_auth_sel(wxCommandEvent
& e
)
359 dh25519_p
->Show(false);
360 switch(auth
->GetSelection()) {
362 dh25519_p
->Show(true);
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()) {
377 ent
.auth
= upload_menu::upload_entry::AUTH_DH25519
;
383 void wxeditor_uploadtarget::revalidate(wxCommandEvent
& e
)
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
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
);
407 std::map
<int, unsigned> id_map
;
415 wxeditor_uploadtargets::wxeditor_uploadtargets(wxWindow
* parent
, upload_menu
* menu
)
416 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Configure upload targets"), wxDefaultPosition
,
421 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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
);
447 top_s
->SetSizeHints(this);
451 void wxeditor_uploadtargets::on_ok(wxCommandEvent
& e
)
456 void wxeditor_uploadtargets::on_add(wxCommandEvent
& e
)
458 auto f
= new wxeditor_uploadtarget(this);
459 int r
= f
->ShowModal();
462 auto used
= umenu
->entries();
463 while(used
.count(ent
)) ent
++;
464 umenu
->configure_entry(ent
, f
->get_entry());
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();
478 umenu
->configure_entry(id_map
[s
], f
->get_entry());
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
);
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
;
510 auto ent
= umenu
->get_entry(i
);
511 list
->Append(towxstring(ent
.name
));
515 //Try to keep selection.
516 if(sel_id
!= 0xFFFFFFFFU
) {
519 if(i
.second
== sel_id
)
522 list
->SetSelection(x
);
523 } else if(sel
< (signed)list
->GetCount())
524 list
->SetSelection(sel
);
527 class wxeditor_uploaddialog
: public wxDialog
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
);
537 void on_game_sel(wxCommandEvent
& e
);
539 struct _timer
: public wxTimer
541 _timer(wxeditor_uploaddialog
* _dialog
) { dialog
= _dialog
; start(); }
542 void start() { Start(500); }
543 void stop() { Stop(); }
546 dialog
->timer_tick();
548 wxeditor_uploaddialog
* dialog
;
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
)
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
;
576 if(incomplete_line
!= "") choices
.insert(incomplete_line
);
578 std::string incomplete_line
;
579 std::set
<std::string
> choices
;
580 } games_output_handler
;
582 wxTextCtrl
* filename
;
584 wxTextCtrl
* description
;
585 std::list
<std::string
> games_list
;
587 wxButton
* game_sel_button
;
588 wxRadioButton
* current
;
590 wxTextCtrl
* ufilename
;
591 wxButton
* file_select
;
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
,
608 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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,
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);
681 void wxeditor_uploaddialog::timer_tick()
684 for(auto i
: upload
->get_messages()) {
685 status
->AppendText(towxstring(i
+ "\n"));
687 if(upload
->finished
) {
690 cancel
->SetLabel(wxT("Close"));
692 auto prog
= upload
->get_progress_ppm();
696 progress
->SetRange(1000000);
697 progress
->SetValue(prog
);
700 } else if(games_req
) {
702 if(games_req
->finished
) {
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();
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"));
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()) {
737 std::string name
= tostdstring(ufilename
->GetValue());
738 auto r
= regex(".*/([^/]+)", name
);
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
);
745 status
->AppendText(towxstring("Can't open '" + tostdstring(ufilename
->GetValue()) + "'\n"));
748 boost::iostreams::copy(in
, rd
);
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();
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());
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();
776 void wxeditor_uploaddialog::on_source_sel(wxCommandEvent
& e
)
778 ufilename
->Enable(file
->GetValue());
779 file_select
->Enable(file
->GetValue());
781 if(current
->GetValue()) {
783 auto prj
= lsnes_instance
.project
->get();
785 curgame
= prj
->gamename
;
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).
799 for(auto& i
: games_list
) {
801 game
->SetLabel(fullname
);
807 if(c
.substr(0, platlen
) == plat
)
811 game
->SetLabel(NO_GAME_NAME
);
816 void wxeditor_uploaddialog::on_file_sel(wxCommandEvent
& e
)
820 f
= pick_file(this, "Pick file to send", ".");
821 } catch(canceled_exception
& e
) {
824 ufilename
->SetValue(towxstring(f
));
827 void wxeditor_uploaddialog::on_game_sel(wxCommandEvent
& e
)
829 auto pos
= game_sel_button
->GetScreenPosition();
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
,
835 if(gs
->ShowModal() != wxID_OK
) {
839 game
->SetLabel(towxstring(gs
->get()));
843 void wxeditor_uploaddialog::on_cancel(wxCommandEvent
& e
)
847 while(!games_req
->finished
)
853 while(!upload
->finished
)
859 EndModal(wxID_CANCEL
);
862 void wxeditor_uploaddialog::on_wclose(wxCloseEvent
& e
)
870 upload_menu::upload_menu(wxWindow
* win
, int wxid_low
, int wxid_high
)
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");
882 while(std::getline(in
, line
)) {
886 if(!n
.field_exists("name") || n
.type_of("name") != JSON::string
)
888 if(!n
.field_exists("url") || n
.type_of("url") != JSON::string
)
890 if(!n
.field_exists("auth") || n
.type_of("auth") != JSON::string
)
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
;
904 entry
.item
= Prepend(wxid_range_low
+ num
, towxstring(entry
.name
+ "..."));
905 destinations
[wxid_range_low
+ num
] = entry
;
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");
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
);
928 case upload_entry::AUTH_DH25519
:
929 n
["auth"] = JSON::string("dh25519");
932 out
<< n
.serialize() << std::endl
;
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
)) {
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
+ "..."));
950 if(destinations
.size() == 0)
952 entry
.item
= Prepend(wxid_range_low
+ num
, towxstring(entry
.name
+ "..."));
953 destinations
[wxid_range_low
+ num
] = entry
;
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
);
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
];
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
);
983 void upload_menu::on_select(wxCommandEvent
& e
)
986 modal_pause_holder hld
;
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
]);
997 } catch(canceled_exception
& e
) {
1000 throw canceled_exception();