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"
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
);
35 const std::string
& str_tolower(const std::string
& x
)
37 static std::map
<std::string
, std::string
> cache
;
41 if(i
>= 'A' && i
<= 'Z')
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
, {" "})) {
63 std::string st
= str_tolower(i
);
64 if(searched
.count(st
))
66 for(size_t j
= 0; j
< name_words
.size(); j
++) {
67 if(is_prefix(st
, name_words
[j
]))
77 class wxwin_gameselect
: public wxDialog
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
);
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.
120 games
->SetStringSelection(dflt
);
126 void on_search_type(wxCommandEvent
& e
)
129 if(games
->GetSelection() != wxNOT_FOUND
)
130 current
= games
->GetStringSelection();
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
));
144 games
->SetStringSelection(current
);
146 void on_list_select(wxCommandEvent
& e
)
148 if(games
->GetSelection() != wxNOT_FOUND
) {
149 chosen
= tostdstring(games
->GetStringSelection());
155 void on_ok(wxCommandEvent
& e
)
159 void on_cancel(wxCommandEvent
& e
)
161 EndModal(wxID_CANCEL
);
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
);
173 return std::make_pair("???", _name
.substr(1));
175 return std::make_pair(_name
.substr(0, r
), _name
.substr(r
+ 1));
178 const std::list
<std::string
>& choices
;
183 std::string old_system
;
188 class wxeditor_uploadtarget
: public wxDialog
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
);
200 void dh25519_fill_box();
208 wxTextCtrl
* dh25519_k
;
212 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow
* parent
)
213 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: New upload target"))
220 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow
* parent
, upload_menu::upload_entry entry
)
221 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Edit upload target: " + entry
.name
))
224 name
->SetValue(towxstring(entry
.name
));
225 url
->SetValue(towxstring(entry
.url
));
227 case upload_menu::upload_entry::AUTH_DH25519
:
228 auth
->SetSelection(0);
235 void wxeditor_uploadtarget::dh25519_fill_box()
239 get_dh25519_pubkey(rbuf
);
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();
247 dh25519_k
->SetValue(towxstring("(Not available)"));
254 void wxeditor_uploadtarget::ctor_common()
257 std::vector
<wxString
> auth_choices
;
259 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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,
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,
268 name
->Connect(wxEVT_COMMAND_TEXT_UPDATED
, wxCommandEventHandler(wxeditor_uploadtarget::revalidate
), NULL
,
270 url
->Connect(wxEVT_COMMAND_TEXT_UPDATED
, wxCommandEventHandler(wxeditor_uploadtarget::revalidate
), NULL
,
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
);
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
);
305 top_s
->SetSizeHints(this);
309 void wxeditor_uploadtarget::on_ok(wxCommandEvent
& e
)
314 void wxeditor_uploadtarget::on_cancel(wxCommandEvent
& e
)
316 EndModal(wxID_CANCEL
);
319 void wxeditor_uploadtarget::generate_dh25519(wxCommandEvent
& e
)
322 std::string entropy
= pick_text(this, "Enter garbage", "Mash some garbage from keyboard to derive\n"
323 "key from:", "", true);
325 highrandom_256(rbuf
+ 0);
326 highrandom_256(rbuf
+ 32);
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);
339 fp
.write((char*)rbuf
, 192);
340 if(!fp
) throw std::runtime_error("Can't write keyfile");
343 } catch(canceled_exception
& e
) {
345 } catch(std::exception
& e
) {
346 show_message_ok(this, "Generate keys error", std::string("Error generating keys:") + e
.what(),
352 void wxeditor_uploadtarget::on_auth_sel(wxCommandEvent
& e
)
354 dh25519_p
->Show(false);
355 switch(auth
->GetSelection()) {
357 dh25519_p
->Show(true);
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()) {
372 ent
.auth
= upload_menu::upload_entry::AUTH_DH25519
;
378 void wxeditor_uploadtarget::revalidate(wxCommandEvent
& e
)
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
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
);
402 std::map
<int, unsigned> id_map
;
410 wxeditor_uploadtargets::wxeditor_uploadtargets(wxWindow
* parent
, upload_menu
* menu
)
411 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Configure upload targets"), wxDefaultPosition
,
416 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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
);
442 top_s
->SetSizeHints(this);
446 void wxeditor_uploadtargets::on_ok(wxCommandEvent
& e
)
451 void wxeditor_uploadtargets::on_add(wxCommandEvent
& e
)
453 auto f
= new wxeditor_uploadtarget(this);
454 int r
= f
->ShowModal();
457 auto used
= umenu
->entries();
458 while(used
.count(ent
)) ent
++;
459 umenu
->configure_entry(ent
, f
->get_entry());
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();
473 umenu
->configure_entry(id_map
[s
], f
->get_entry());
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
);
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
;
505 auto ent
= umenu
->get_entry(i
);
506 list
->Append(towxstring(ent
.name
));
510 //Try to keep selection.
511 if(sel_id
!= 0xFFFFFFFFU
) {
514 if(i
.second
== sel_id
)
517 list
->SetSelection(x
);
518 } else if(sel
< list
->GetCount())
519 list
->SetSelection(sel
);
522 class wxeditor_uploaddialog
: public wxDialog
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
);
532 void on_game_sel(wxCommandEvent
& e
);
534 struct _timer
: public wxTimer
536 _timer(wxeditor_uploaddialog
* _dialog
) { dialog
= _dialog
; start(); }
537 void start() { Start(500); }
538 void stop() { Stop(); }
541 dialog
->timer_tick();
543 wxeditor_uploaddialog
* dialog
;
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
)
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
;
571 if(incomplete_line
!= "") choices
.insert(incomplete_line
);
573 std::string incomplete_line
;
574 std::set
<std::string
> choices
;
575 } games_output_handler
;
577 wxTextCtrl
* filename
;
579 wxTextCtrl
* description
;
580 std::list
<std::string
> games_list
;
582 wxButton
* game_sel_button
;
583 wxRadioButton
* current
;
585 wxTextCtrl
* ufilename
;
586 wxButton
* file_select
;
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
,
603 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
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,
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);
676 void wxeditor_uploaddialog::timer_tick()
679 for(auto i
: upload
->get_messages()) {
680 status
->AppendText(towxstring(i
+ "\n"));
682 if(upload
->finished
) {
685 cancel
->SetLabel(wxT("Close"));
687 auto prog
= upload
->get_progress_ppm();
691 progress
->SetRange(1000000);
692 progress
->SetValue(prog
);
695 } else if(games_req
) {
697 if(games_req
->finished
) {
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();
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"));
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()) {
732 std::string name
= tostdstring(ufilename
->GetValue());
733 auto r
= regex(".*/([^/]+)", name
);
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
);
740 status
->AppendText(towxstring("Can't open '" + tostdstring(ufilename
->GetValue()) + "'\n"));
743 boost::iostreams::copy(in
, rd
);
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();
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());
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();
771 void wxeditor_uploaddialog::on_source_sel(wxCommandEvent
& e
)
773 ufilename
->Enable(file
->GetValue());
774 file_select
->Enable(file
->GetValue());
776 if(current
->GetValue()) {
778 auto prj
= project_get();
780 curgame
= prj
->gamename
;
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).
793 for(auto& i
: games_list
) {
795 game
->SetLabel(fullname
);
801 if(c
.substr(0, platlen
) == plat
)
805 game
->SetLabel(NO_GAME_NAME
);
810 void wxeditor_uploaddialog::on_file_sel(wxCommandEvent
& e
)
814 f
= pick_file(this, "Pick file to send", ".");
815 } catch(canceled_exception
& e
) {
818 ufilename
->SetValue(towxstring(f
));
821 void wxeditor_uploaddialog::on_game_sel(wxCommandEvent
& e
)
823 auto pos
= game_sel_button
->GetScreenPosition();
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
,
829 if(gs
->ShowModal() != wxID_OK
) {
833 game
->SetLabel(towxstring(gs
->get()));
837 void wxeditor_uploaddialog::on_cancel(wxCommandEvent
& e
)
841 while(!games_req
->finished
)
847 while(!upload
->finished
)
853 EndModal(wxID_CANCEL
);
856 void wxeditor_uploaddialog::on_wclose(wxCloseEvent
& e
)
864 upload_menu::upload_menu(wxWindow
* win
, int wxid_low
, int wxid_high
)
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");
876 while(std::getline(in
, line
)) {
880 if(!n
.field_exists("name") || n
.type_of("name") != JSON::string
)
882 if(!n
.field_exists("url") || n
.type_of("url") != JSON::string
)
884 if(!n
.field_exists("auth") || n
.type_of("auth") != JSON::string
)
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
;
898 entry
.item
= Prepend(wxid_range_low
+ num
, towxstring(entry
.name
+ "..."));
899 destinations
[wxid_range_low
+ num
] = entry
;
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");
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
);
922 case upload_entry::AUTH_DH25519
:
923 n
["auth"] = JSON::string("dh25519");
926 out
<< n
.serialize() << std::endl
;
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
)) {
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
+ "..."));
944 if(destinations
.size() == 0)
946 entry
.item
= Prepend(wxid_range_low
+ num
, towxstring(entry
.name
+ "..."));
947 destinations
[wxid_range_low
+ num
] = entry
;
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
);
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
];
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
);
977 void upload_menu::on_select(wxCommandEvent
& e
)
980 modal_pause_holder hld
;
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
]);
991 } catch(canceled_exception
& e
) {
994 throw canceled_exception();