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 "core/ui-services.hpp"
11 #include "library/directory.hpp"
12 #include "library/skein.hpp"
13 #include "library/zip.hpp"
14 #include "library/json.hpp"
15 #include "library/string.hpp"
21 #include <wx/control.h>
22 #include <wx/combobox.h>
23 #include <wx/spinctrl.h>
24 #include <boost/iostreams/categories.hpp>
25 #include <boost/iostreams/copy.hpp>
26 #include <boost/iostreams/stream.hpp>
27 #include <boost/iostreams/stream_buffer.hpp>
28 #include <boost/iostreams/filter/symmetric.hpp>
29 #include <boost/iostreams/filter/zlib.hpp>
30 #include <boost/iostreams/filtering_stream.hpp>
31 #include <boost/iostreams/device/back_inserter.hpp>
33 #define NO_GAME_NAME "(default)"
35 std::string
pick_file(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
);
39 const std::string
& str_tolower(const std::string
& x
)
41 static std::map
<std::string
, std::string
> cache
;
45 if(i
>= 'A' && i
<= 'Z')
54 inline bool is_prefix(std::string a
, std::string b
)
56 return (a
.length() <= b
.length() && b
.substr(0, a
.length()) == a
);
59 bool search_match(const std::string
& term
, const std::string
& name
)
61 std::set
<std::string
> searched
;
62 std::vector
<std::string
> name_words
;
63 for(auto i
: token_iterator
<char>::foreach(name
, {" "})) if(i
!= "") name_words
.push_back(str_tolower(i
));
64 for(auto i
: token_iterator
<char>::foreach(term
, {" "})) {
67 std::string st
= str_tolower(i
);
68 if(searched
.count(st
))
70 for(size_t j
= 0; j
< name_words
.size(); j
++) {
71 if(is_prefix(st
, name_words
[j
]))
81 class wxwin_gameselect
: public wxDialog
84 wxwin_gameselect(wxWindow
* parent
, const std::list
<std::string
>& _choices
, const std::string
& dflt
,
85 const std::string
& system
, int x
, int y
)
86 : wxDialog(parent
, wxID_ANY
, wxT("lsnes: Pick a game"), wxPoint(x
, y
)),
87 chosen(dflt
), choices(_choices
)
90 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
93 top_s
->Add(new wxStaticText(this, wxID_ANY
, wxT("Search:")), 0, wxGROW
);
94 top_s
->Add(search
= new wxTextCtrl(this, wxID_ANY
, wxT("")), 1, wxGROW
);
95 top_s
->Add(new wxStaticText(this, wxID_ANY
, wxT("Game:")), 0, wxGROW
);
96 top_s
->Add(games
= new wxListBox(this, wxID_ANY
, wxDefaultPosition
, wxSize(400, 300)), 1, wxGROW
);
98 search
->Connect(wxEVT_COMMAND_TEXT_UPDATED
,
99 wxCommandEventHandler(wxwin_gameselect::on_search_type
), NULL
, this);
100 games
->Connect(wxEVT_COMMAND_LISTBOX_SELECTED
,
101 wxCommandEventHandler(wxwin_gameselect::on_list_select
), NULL
, this);
103 wxBoxSizer
* pbutton_s
= new wxBoxSizer(wxHORIZONTAL
);
104 pbutton_s
->AddStretchSpacer();
105 pbutton_s
->Add(ok
= new wxButton(this, wxID_OK
, wxT("OK")), 0, wxGROW
);
106 pbutton_s
->Add(cancel
= new wxButton(this, wxID_CANCEL
, wxT("Cancel")), 0, wxGROW
);
107 ok
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
108 wxCommandEventHandler(wxwin_gameselect::on_ok
), NULL
, this);
109 cancel
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
110 wxCommandEventHandler(wxwin_gameselect::on_cancel
), NULL
, this);
111 top_s
->Add(pbutton_s
, 0, wxGROW
);
113 auto dsplit
= split_name(dflt
);
114 bool wrong_default
= (dflt
!= NO_GAME_NAME
&& system
!= "" && dsplit
.first
!= system
);
115 if(system
!= "" && !wrong_default
) {
116 //Populate just one system.
124 games
->SetStringSelection(towxstring(dflt
));
130 void on_search_type(wxCommandEvent
& e
)
134 if(games
->GetSelection() != wxNOT_FOUND
)
135 current
= games
->GetStringSelection();
137 std::string terms
= tostdstring(search
->GetValue());
138 for(auto& i
: choices
) {
139 auto g
= split_name(i
);
140 if(i
!= current
&& i
!= NO_GAME_NAME
) {
141 if(oneplat
&& g
.first
!= rsystem
)
142 continue; //Wrong system.
143 if(!search_match(terms
, i
))
144 continue; //Doesn't match terms.
146 games
->Append(towxstring(i
));
149 games
->SetStringSelection(current
);
151 void on_list_select(wxCommandEvent
& e
)
154 if(games
->GetSelection() != wxNOT_FOUND
) {
155 chosen
= tostdstring(games
->GetStringSelection());
161 void on_ok(wxCommandEvent
& e
)
166 void on_cancel(wxCommandEvent
& e
)
169 EndModal(wxID_CANCEL
);
172 std::pair
<std::string
, std::string
> split_name(const std::string
& name
)
174 if(name
== NO_GAME_NAME
)
175 return std::make_pair("N/A", NO_GAME_NAME
);
176 std::string _name
= name
;
177 size_t r
= _name
.find_first_of(" ");
178 if(r
>= _name
.length())
179 return std::make_pair("???", name
);
181 return std::make_pair("???", _name
.substr(1));
183 return std::make_pair(_name
.substr(0, r
), _name
.substr(r
+ 1));
186 const std::list
<std::string
>& choices
;
191 std::string old_system
;
196 class wxeditor_uploadtarget
: public wxDialog
199 wxeditor_uploadtarget(wxWindow
* parent
);
200 wxeditor_uploadtarget(wxWindow
* parent
, upload_menu::upload_entry entry
);
201 upload_menu::upload_entry
get_entry();
202 void generate_dh25519(wxCommandEvent
& e
);
203 void on_ok(wxCommandEvent
& e
);
204 void on_cancel(wxCommandEvent
& e
);
205 void on_auth_sel(wxCommandEvent
& e
);
206 void revalidate(wxCommandEvent
& e
);
208 void dh25519_fill_box();
216 wxTextCtrl
* dh25519_k
;
220 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow
* parent
)
221 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: New upload target"))
229 wxeditor_uploadtarget::wxeditor_uploadtarget(wxWindow
* parent
, upload_menu::upload_entry entry
)
230 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Edit upload target: " + entry
.name
))
234 name
->SetValue(towxstring(entry
.name
));
235 url
->SetValue(towxstring(entry
.url
));
237 case upload_menu::upload_entry::AUTH_DH25519
:
238 auth
->SetSelection(0);
245 void wxeditor_uploadtarget::dh25519_fill_box()
250 get_dh25519_pubkey(rbuf
);
253 for(unsigned i
= 0; i
< 32; i
++)
254 sprintf(out
+ 2 * i
, "%02x", rbuf
[i
]);
255 dh25519_k
->SetValue(towxstring(out
));
256 dh25519_g
->Disable();
258 dh25519_k
->SetValue(towxstring("(Not available)"));
265 void wxeditor_uploadtarget::ctor_common()
269 std::vector
<wxString
> auth_choices
;
271 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
274 top_s
->Add(new wxStaticText(this, wxID_ANY
, towxstring("Name")), 0, wxGROW
);
275 top_s
->Add(name
= new wxTextCtrl(this, wxID_ANY
, towxstring(""), wxDefaultPosition
, wxSize(550, -1)), 0,
277 top_s
->Add(new wxStaticText(this, wxID_ANY
, towxstring("URL")), 0, wxGROW
);
278 top_s
->Add(url
= new wxTextCtrl(this, wxID_ANY
, towxstring(""), wxDefaultPosition
, wxSize(550, -1)), 0,
280 name
->Connect(wxEVT_COMMAND_TEXT_UPDATED
, wxCommandEventHandler(wxeditor_uploadtarget::revalidate
), NULL
,
282 url
->Connect(wxEVT_COMMAND_TEXT_UPDATED
, wxCommandEventHandler(wxeditor_uploadtarget::revalidate
), NULL
,
285 top_s
->Add(new wxStaticText(this, wxID_ANY
, towxstring("Authentication")), 0, wxGROW
);
286 auth_choices
.push_back(towxstring("dh25519"));
287 top_s
->Add(auth
= new wxComboBox(this, wxID_ANY
, auth_choices
[0], wxDefaultPosition
, wxDefaultSize
,
288 auth_choices
.size(), &auth_choices
[0], wxCB_READONLY
), 0, wxGROW
);
290 dh25519_p
= new wxPanel(this, wxID_ANY
);
291 wxBoxSizer
* dh25519_s
= new wxBoxSizer(wxVERTICAL
);
292 dh25519_p
->SetSizer(dh25519_s
);
293 wxStaticBox
* dh25519_b
= new wxStaticBox(dh25519_p
, wxID_ANY
, towxstring("Authentication parameters"));
294 wxStaticBoxSizer
* dh25519_s2
= new wxStaticBoxSizer(dh25519_b
, wxVERTICAL
);
295 top_s
->Add(dh25519_p
, 0, wxGROW
);
296 dh25519_s
->Add(dh25519_s2
, 0, wxGROW
);
297 dh25519_s2
->Add(new wxStaticText(dh25519_p
, wxID_ANY
, towxstring("Key")), 0, wxGROW
);
298 dh25519_s2
->Add(dh25519_k
= new wxTextCtrl(dh25519_p
, wxID_ANY
, towxstring(""), wxDefaultPosition
,
299 wxSize(550, -1), wxTE_READONLY
), 0, wxGROW
);
300 dh25519_s2
->Add(dh25519_g
= new wxButton(dh25519_p
, wxID_ANY
, towxstring("Generate")), 0, wxGROW
);
301 dh25519_s
->SetSizeHints(dh25519_p
);
304 wxBoxSizer
* pbutton_s
= new wxBoxSizer(wxHORIZONTAL
);
305 pbutton_s
->AddStretchSpacer();
306 pbutton_s
->Add(ok
= new wxButton(this, wxID_OK
, wxT("OK")), 0, wxGROW
);
307 pbutton_s
->Add(cancel
= new wxButton(this, wxID_CANCEL
, wxT("Cancel")), 0, wxGROW
);
308 ok
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
309 wxCommandEventHandler(wxeditor_uploadtarget::on_ok
), NULL
, this);
310 cancel
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
311 wxCommandEventHandler(wxeditor_uploadtarget::on_cancel
), NULL
, this);
312 top_s
->Add(pbutton_s
, 0, wxGROW
);
317 top_s
->SetSizeHints(this);
321 void wxeditor_uploadtarget::on_ok(wxCommandEvent
& e
)
327 void wxeditor_uploadtarget::on_cancel(wxCommandEvent
& e
)
330 EndModal(wxID_CANCEL
);
333 void wxeditor_uploadtarget::generate_dh25519(wxCommandEvent
& e
)
338 std::string entropy
= pick_text(this, "Enter garbage", "Mash some garbage from keyboard to derive\n"
339 "key from:", "", true);
340 highrandom_256(rbuf
+ 0);
341 highrandom_256(rbuf
+ 32);
343 x
.resize(entropy
.length());
344 std::copy(entropy
.begin(), entropy
.end(), x
.begin());
345 skein::hash
h(skein::hash::PIPE_1024
, 1024);
346 h
.write((uint8_t*)&x
[0], x
.size());
347 h
.read((uint8_t*)rbuf
+ 64);
349 std::ofstream
fp(get_config_path() + "/dh25519.key", std::ios::binary
);
350 if(!fp
) throw std::runtime_error("Can't open keyfile");
351 #if !defined(_WIN32) && !defined(_WIN64)
352 chmod((get_config_path() + "/dh25519.key").c_str(), 0600);
354 fp
.write((char*)rbuf
, 192);
355 if(!fp
) throw std::runtime_error("Can't write keyfile");
357 skein::zeroize(rbuf
, sizeof(rbuf
));
359 } catch(canceled_exception
& e
) {
360 skein::zeroize(rbuf
, sizeof(rbuf
));
362 } catch(std::exception
& e
) {
363 skein::zeroize(rbuf
, sizeof(rbuf
));
364 show_message_ok(this, "Generate keys error", std::string("Error generating keys:") + e
.what(),
370 void wxeditor_uploadtarget::on_auth_sel(wxCommandEvent
& e
)
373 dh25519_p
->Show(false);
374 switch(auth
->GetSelection()) {
376 dh25519_p
->Show(true);
383 upload_menu::upload_entry
wxeditor_uploadtarget::get_entry()
386 upload_menu::upload_entry ent
;
387 ent
.name
= tostdstring(name
->GetValue());
388 ent
.url
= tostdstring(url
->GetValue());
389 ent
.auth
= upload_menu::upload_entry::AUTH_DH25519
;
390 switch(auth
->GetSelection()) {
392 ent
.auth
= upload_menu::upload_entry::AUTH_DH25519
;
398 void wxeditor_uploadtarget::revalidate(wxCommandEvent
& e
)
402 if(!name
|| (name
->GetValue().Length() == 0)) valid
= false;
403 std::string URL
= url
? tostdstring(url
->GetValue()) : "";
404 if(!regex_match("https?://(([!$&'()*+,;=:A-Za-z0-9._~-]|%[0-9A-Fa-f][0-9A-Fa-f])+|\\[v[0-9A-Fa-f]\\."
405 "([!$&'()*+,;=:A-Za-z0-9._~-]|%[0-9A-Fa-f][0-9A-Fa-f])+\\]|\\[[0-9A-Fa-f:]+\\])"
406 "(/([!$&'()*+,;=:A-Za-z0-9._~@-]|%[0-9A-Fa-f][0-9A-Fa-f])+)*", URL
)) valid
= false;
407 if(!auth
|| (auth
->GetSelection() == 0 && dh25519_g
->IsEnabled())) valid
= false;
408 if(ok
) ok
->Enable(valid
);
411 class wxeditor_uploadtargets
: public wxDialog
414 wxeditor_uploadtargets(wxWindow
* parent
, upload_menu
* menu
);
415 void on_ok(wxCommandEvent
& e
);
416 void on_add(wxCommandEvent
& e
);
417 void on_modify(wxCommandEvent
& e
);
418 void on_remove(wxCommandEvent
& e
);
419 void on_list_sel(wxCommandEvent
& e
);
423 std::map
<int, unsigned> id_map
;
431 wxeditor_uploadtargets::wxeditor_uploadtargets(wxWindow
* parent
, upload_menu
* menu
)
432 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Configure upload targets"), wxDefaultPosition
,
438 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
441 top_s
->Add(list
= new wxListBox(this, wxID_ANY
), 1, wxGROW
);
442 list
->Connect(wxEVT_COMMAND_LISTBOX_SELECTED
,
443 wxCommandEventHandler(wxeditor_uploadtargets::on_list_sel
), NULL
, this);
445 wxBoxSizer
* pbutton_s
= new wxBoxSizer(wxHORIZONTAL
);
446 pbutton_s
->Add(ok
= new wxButton(this, wxID_OK
, wxT("OK")), 0, wxGROW
);
447 pbutton_s
->AddStretchSpacer();
448 pbutton_s
->Add(add
= new wxButton(this, wxID_ADD
, wxT("Add")), 0, wxGROW
);
449 pbutton_s
->Add(modify
= new wxButton(this, wxID_EDIT
, wxT("Modify")), 0, wxGROW
);
450 pbutton_s
->Add(_delete
= new wxButton(this, wxID_DELETE
, wxT("Delete")), 0, wxGROW
);
451 modify
->Enable(false);
452 _delete
->Enable(false);
453 ok
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
454 wxCommandEventHandler(wxeditor_uploadtargets::on_ok
), NULL
, this);
455 add
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
456 wxCommandEventHandler(wxeditor_uploadtargets::on_add
), NULL
, this);
457 modify
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
458 wxCommandEventHandler(wxeditor_uploadtargets::on_modify
), NULL
, this);
459 _delete
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
460 wxCommandEventHandler(wxeditor_uploadtargets::on_remove
), NULL
, this);
461 top_s
->Add(pbutton_s
, 0, wxGROW
);
464 top_s
->SetSizeHints(this);
468 void wxeditor_uploadtargets::on_ok(wxCommandEvent
& e
)
474 void wxeditor_uploadtargets::on_add(wxCommandEvent
& e
)
477 auto f
= new wxeditor_uploadtarget(this);
478 int r
= f
->ShowModal();
481 auto used
= umenu
->entries();
482 while(used
.count(ent
)) ent
++;
483 umenu
->configure_entry(ent
, f
->get_entry());
489 void wxeditor_uploadtargets::on_modify(wxCommandEvent
& e
)
492 auto s
= list
->GetSelection();
493 if(s
== wxNOT_FOUND
) return;
494 if(!id_map
.count(s
)) return;
495 auto f
= new wxeditor_uploadtarget(this, umenu
->get_entry(id_map
[s
]));
496 int r
= f
->ShowModal();
498 umenu
->configure_entry(id_map
[s
], f
->get_entry());
503 void wxeditor_uploadtargets::on_remove(wxCommandEvent
& e
)
506 auto s
= list
->GetSelection();
507 if(s
== wxNOT_FOUND
) return;
508 if(!id_map
.count(s
)) return;
509 unsigned id
= id_map
[s
];
510 umenu
->delete_entry(id
);
514 void wxeditor_uploadtargets::on_list_sel(wxCommandEvent
& e
)
517 auto s
= list
->GetSelection();
518 modify
->Enable(s
!= wxNOT_FOUND
);
519 _delete
->Enable(s
!= wxNOT_FOUND
);
522 void wxeditor_uploadtargets::refresh()
525 auto ents
= umenu
->entries();
526 auto sel
= list
->GetSelection();
527 auto sel_id
= id_map
.count(sel
) ? id_map
[sel
] : 0xFFFFFFFFU
;
533 auto ent
= umenu
->get_entry(i
);
534 list
->Append(towxstring(ent
.name
));
538 //Try to keep selection.
539 if(sel_id
!= 0xFFFFFFFFU
) {
542 if(i
.second
== sel_id
)
545 list
->SetSelection(x
);
546 } else if(sel
< (signed)list
->GetCount())
547 list
->SetSelection(sel
);
550 class wxeditor_uploaddialog
: public wxDialog
553 wxeditor_uploaddialog(wxWindow
* parent
, emulator_instance
& inst
, upload_menu::upload_entry entry
);
554 void on_ok(wxCommandEvent
& e
);
555 void on_cancel(wxCommandEvent
& e
);
556 void on_source_sel(wxCommandEvent
& e
);
557 void on_file_sel(wxCommandEvent
& e
);
558 void on_wclose(wxCloseEvent
& e
);
560 void on_game_sel(wxCommandEvent
& e
);
562 struct _timer
: public wxTimer
564 _timer(wxeditor_uploaddialog
* _dialog
) { dialog
= _dialog
; start(); }
565 void start() { Start(500); }
566 void stop() { Stop(); }
569 dialog
->timer_tick();
571 wxeditor_uploaddialog
* dialog
;
573 struct _games_output_handler
: public http_request::output_handler
{
574 ~_games_output_handler()
577 void header(const std::string
& name
, const std::string
& cotent
)
581 void write(const char* source
, size_t srcsize
)
583 std::string
x(source
, srcsize
);
584 while(x
.find_first_of("\n") < x
.length()) {
585 size_t split
= x
.find_first_of("\n");
586 std::string line
= x
.substr(0, split
);
587 x
= x
.substr(split
+ 1);
588 incomplete_line
+= line
;
589 while(incomplete_line
.length() > 0 &&
590 incomplete_line
[incomplete_line
.length() - 1] == '\r')
591 incomplete_line
= incomplete_line
.substr(0, incomplete_line
.length() - 1);
592 choices
.insert(incomplete_line
);
593 incomplete_line
= "";
595 if(x
!= "") incomplete_line
+= x
;
599 if(incomplete_line
!= "") choices
.insert(incomplete_line
);
601 std::string incomplete_line
;
602 std::set
<std::string
> choices
;
603 } games_output_handler
;
604 emulator_instance
& inst
;
606 wxTextCtrl
* filename
;
608 wxTextCtrl
* description
;
609 std::list
<std::string
> games_list
;
611 wxButton
* game_sel_button
;
612 wxRadioButton
* current
;
614 wxTextCtrl
* ufilename
;
615 wxButton
* file_select
;
621 http_async_request
* games_req
;
622 upload_menu::upload_entry _entry
;
625 wxeditor_uploaddialog::wxeditor_uploaddialog(wxWindow
* parent
, emulator_instance
& _inst
,
626 upload_menu::upload_entry entry
)
627 : wxDialog(parent
, wxID_ANY
, towxstring("lsnes: Upload file: " + entry
.name
), wxDefaultPosition
,
628 wxSize(-1, -1)), inst(_inst
)
634 wxBoxSizer
* top_s
= new wxBoxSizer(wxVERTICAL
);
637 Connect(wxEVT_CLOSE_WINDOW
, wxCloseEventHandler(wxeditor_uploaddialog::on_wclose
), NULL
, this);
639 top_s
->Add(new wxStaticText(this, wxID_ANY
, wxT("Filename:")), 0, wxGROW
);
640 top_s
->Add(filename
= new wxTextCtrl(this, wxID_ANY
, wxT(""), wxDefaultPosition
, wxSize(550, -1)), 0,
642 top_s
->Add(new wxStaticText(this, wxID_ANY
, wxT("Title:")), 0, wxGROW
);
643 top_s
->Add(title
= new wxTextCtrl(this, wxID_ANY
, wxT(""), wxDefaultPosition
, wxSize(550, -1)), 0, wxGROW
);
644 top_s
->Add(new wxStaticText(this, wxID_ANY
, wxT("Description:")), 0, wxGROW
);
645 top_s
->Add(description
= new wxTextCtrl(this, wxID_ANY
, wxT(""), wxDefaultPosition
, wxSize(550, 300),
646 wxTE_MULTILINE
), 0, wxGROW
);
647 wxBoxSizer
* game_s
= new wxBoxSizer(wxHORIZONTAL
);
648 game_s
->Add(new wxStaticText(this, wxID_ANY
, wxT("Game:")), 0, wxGROW
);
649 game_s
->Add(game
= new wxStaticText(this, wxID_ANY
, wxT(NO_GAME_NAME
)), 1, wxGROW
);
650 game_s
->Add(game_sel_button
= new wxButton(this, wxID_ANY
, wxT("Select")), 0, wxGROW
);
651 top_s
->Add(game_s
, 0, wxGROW
);
652 games_list
.push_back(NO_GAME_NAME
);
653 game_sel_button
->Enable(false);
654 game_sel_button
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
655 wxCommandEventHandler(wxeditor_uploaddialog::on_game_sel
), NULL
, this);
656 top_s
->Add(hidden
= new wxCheckBox(this, wxID_ANY
, wxT("Hidden")), 0, wxGROW
);
658 top_s
->Add(current
= new wxRadioButton(this, wxID_ANY
, wxT("Current movie"), wxDefaultPosition
, wxDefaultSize
,
659 wxRB_GROUP
), 0, wxGROW
);
660 top_s
->Add(file
= new wxRadioButton(this, wxID_ANY
, wxT("Specified file:")), 0, wxGROW
);
661 current
->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED
,
662 wxCommandEventHandler(wxeditor_uploaddialog::on_source_sel
), NULL
, this);
663 file
->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED
,
664 wxCommandEventHandler(wxeditor_uploaddialog::on_source_sel
), NULL
, this);
665 if(!UI_has_movie(inst
)) {
666 current
->Enable(false);
667 file
->SetValue(true);
670 wxBoxSizer
* file_s
= new wxBoxSizer(wxHORIZONTAL
);
671 file_s
->Add(ufilename
= new wxTextCtrl(this, wxID_ANY
, wxT("")), 1, wxGROW
);
672 file_s
->Add(file_select
= new wxButton(this, wxID_ANY
, wxT("...")), 0, wxGROW
);
673 top_s
->Add(file_s
, 0, wxGROW
);
674 file_select
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
675 wxCommandEventHandler(wxeditor_uploaddialog::on_file_sel
), NULL
, this);
676 ufilename
->Enable(file
->GetValue());
677 file_select
->Enable(file
->GetValue());
679 top_s
->Add(status
= new wxTextCtrl(this, wxID_ANY
, wxT(""), wxDefaultPosition
, wxSize(550, 300),
680 wxTE_READONLY
| wxTE_MULTILINE
), 0, wxGROW
);
681 top_s
->Add(progress
= new wxGauge(this, wxID_ANY
, 1000000, wxDefaultPosition
, wxSize(-1, 15),
682 wxGA_HORIZONTAL
), 0, wxGROW
);
684 status
->AppendText(wxT("Obtaining list of games...\n"));
685 games_req
= new http_async_request();
686 games_req
->verb
= "GET";
687 games_req
->url
= entry
.url
+ "/games";
688 games_req
->ihandler
= NULL
;
689 games_req
->ohandler
= &games_output_handler
;
690 games_req
->lauch_async();
692 timer
= new _timer(this);
694 wxBoxSizer
* pbutton_s
= new wxBoxSizer(wxHORIZONTAL
);
695 pbutton_s
->AddStretchSpacer();
696 pbutton_s
->Add(ok
= new wxButton(this, wxID_OK
, wxT("Upload")), 0, wxGROW
);
697 ok
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
698 wxCommandEventHandler(wxeditor_uploaddialog::on_ok
), NULL
, this);
699 pbutton_s
->Add(cancel
= new wxButton(this, wxID_OK
, wxT("Cancel")), 0, wxGROW
);
700 cancel
->Connect(wxEVT_COMMAND_BUTTON_CLICKED
,
701 wxCommandEventHandler(wxeditor_uploaddialog::on_cancel
), NULL
, this);
702 top_s
->Add(pbutton_s
, 0, wxGROW
);
703 top_s
->SetSizeHints(this);
707 void wxeditor_uploaddialog::timer_tick()
711 for(auto i
: upload
->get_messages()) {
712 status
->AppendText(towxstring(i
+ "\n"));
714 if(upload
->finished
) {
717 cancel
->SetLabel(wxT("Close"));
719 auto prog
= upload
->get_progress_ppm();
723 progress
->SetRange(1000000);
724 progress
->SetValue(prog
);
727 } else if(games_req
) {
729 if(games_req
->finished
) {
731 games_output_handler
.flush();
732 if(games_req
->errormsg
!= "") {
733 msg
= (stringfmt() << "Error getting list of games: " << (games_req
->errormsg
)).str();
734 } else if(games_req
->http_code
!= 200) {
735 msg
= (stringfmt() << "Got unexpected HTTP status " << (games_req
->http_code
)).str();
737 for(auto i
: games_output_handler
.choices
)
738 games_list
.push_back(i
);
739 if(games_list
.size() > 1)
740 game_sel_button
->Enable(true);
741 msg
= "Got list of games.";
743 status
->AppendText(towxstring(msg
+ "\n"));
751 progress
->SetRange(1000000);
752 progress
->SetValue(0);
757 void wxeditor_uploaddialog::on_ok(wxCommandEvent
& e
)
760 if(file
->GetValue() && ufilename
->GetValue().Length() == 0) return;
761 std::string fn
= tostdstring(filename
->GetValue());
762 std::vector
<char> content
;
763 if(file
->GetValue()) {
765 std::string name
= tostdstring(ufilename
->GetValue());
766 auto r
= regex(".*/([^/]+)", name
);
768 filename
->SetValue(towxstring(name
));
770 boost::iostreams::back_insert_device
<std::vector
<char>> rd(content
);
771 std::ifstream
in(tostdstring(ufilename
->GetValue()), std::ios::binary
);
773 status
->AppendText(towxstring("Can't open '" + tostdstring(ufilename
->GetValue()) + "'\n"));
776 boost::iostreams::copy(in
, rd
);
778 if(fn
.length() < 6 || fn
.substr(fn
.length() - 5) != ".lsmv")
779 filename
->SetValue(towxstring(fn
+ ".lsmv"));
780 std::ostringstream stream
;
781 UI_save_movie(inst
, stream
);
782 std::string _stream
= stream
.str();
783 content
= std::vector
<char>(_stream
.begin(), _stream
.end());
786 upload
= new file_upload();
787 upload
->base_url
= _entry
.url
;
788 upload
->content
= content
;
789 upload
->filename
= tostdstring(filename
->GetValue());
790 upload
->title
= tostdstring(title
->GetValue());
791 upload
->description
= tostdstring(description
->GetValue());
792 upload
->gamename
= tostdstring(game
->GetLabel());
793 upload
->hidden
= hidden
->GetValue();
797 void wxeditor_uploaddialog::on_source_sel(wxCommandEvent
& e
)
800 ufilename
->Enable(file
->GetValue());
801 file_select
->Enable(file
->GetValue());
803 if(current
->GetValue()) {
804 auto g
= UI_lookup_platform_and_game(inst
);
805 std::string plat
= g
.first
+ " ";
806 std::string curgame
= g
.second
;
807 size_t platlen
= plat
.length();
808 std::string c
= tostdstring(game
->GetLabel());
809 std::string fullname
= plat
+ curgame
;
810 //The rules here are:
811 //If there is fullname among games, select that.
812 //If not and the previous selection has the same system, keep it.
813 //Otherwise select (default).
815 for(auto& i
: games_list
) {
817 game
->SetLabel(fullname
);
823 if(c
.substr(0, platlen
) == plat
)
827 game
->SetLabel(NO_GAME_NAME
);
832 void wxeditor_uploaddialog::on_file_sel(wxCommandEvent
& e
)
837 f
= pick_file(this, "Pick file to send", ".");
838 } catch(canceled_exception
& e
) {
841 ufilename
->SetValue(towxstring(f
));
844 void wxeditor_uploaddialog::on_game_sel(wxCommandEvent
& e
)
847 auto pos
= game_sel_button
->GetScreenPosition();
849 if(current
->GetValue()) {
850 system
= UI_lookup_platform_and_game(inst
).first
;
852 wxwin_gameselect
* gs
= new wxwin_gameselect(this, games_list
, tostdstring(game
->GetLabel()), system
,
854 if(gs
->ShowModal() != wxID_OK
) {
858 game
->SetLabel(towxstring(gs
->get()));
862 void wxeditor_uploaddialog::on_cancel(wxCommandEvent
& e
)
867 while(!games_req
->finished
)
873 while(!upload
->finished
)
879 EndModal(wxID_CANCEL
);
882 void wxeditor_uploaddialog::on_wclose(wxCloseEvent
& e
)
891 upload_menu::upload_menu(wxWindow
* win
, emulator_instance
& _inst
, int wxid_low
, int wxid_high
)
896 wxid_range_low
= wxid_low
;
897 wxid_range_high
= wxid_high
;
898 Append(wxid_range_high
, towxstring("Configure..."));
899 win
->Connect(wxid_low
, wxid_high
, wxEVT_COMMAND_MENU_SELECTED
,
900 wxCommandEventHandler(upload_menu::on_select
), NULL
, this);
902 std::ifstream
in(get_config_path() + "/upload.cfg");
905 while(std::getline(in
, line
)) {
909 if(!n
.field_exists("name") || n
.type_of("name") != JSON::string
)
911 if(!n
.field_exists("url") || n
.type_of("url") != JSON::string
)
913 if(!n
.field_exists("auth") || n
.type_of("auth") != JSON::string
)
915 entry
.name
= n
["name"].as_string8();
916 entry
.url
= n
["url"].as_string8();
917 std::string auth
= n
["auth"].as_string8();
918 if(auth
== "dh25519")
919 entry
.auth
= upload_entry::AUTH_DH25519
;
927 entry
.item
= Prepend(wxid_range_low
+ num
, towxstring(entry
.name
+ "..."));
928 destinations
[wxid_range_low
+ num
] = entry
;
934 upload_menu::~upload_menu()
938 void upload_menu::save()
940 std::string base
= get_config_path() + "/upload.cfg";
942 std::ofstream
out(base
+ ".tmp");
945 for(auto i
: destinations
) {
946 upload_entry entry
= i
.second
;
947 JSON::node
n(JSON::object
);
948 n
["name"] = JSON::string(entry
.name
);
949 n
["url"] = JSON::string(entry
.url
);
951 case upload_entry::AUTH_DH25519
:
952 n
["auth"] = JSON::string("dh25519");
955 out
<< n
.serialize() << std::endl
;
960 directory::rename_overwrite((base
+ ".tmp").c_str(), base
.c_str());
963 void upload_menu::configure_entry(unsigned num
, struct upload_entry entry
)
966 if(destinations
.count(wxid_range_low
+ num
)) {
968 auto tmp
= destinations
[wxid_range_low
+ num
].item
;
969 destinations
[wxid_range_low
+ num
] = entry
;
970 destinations
[wxid_range_low
+ num
].item
= tmp
;
971 destinations
[wxid_range_low
+ num
].item
->SetItemLabel(towxstring(entry
.name
+ "..."));
974 if(destinations
.size() == 0)
976 entry
.item
= Prepend(wxid_range_low
+ num
, towxstring(entry
.name
+ "..."));
977 destinations
[wxid_range_low
+ num
] = entry
;
982 std::set
<unsigned> upload_menu::entries()
984 std::set
<unsigned> r
;
985 for(auto i
: destinations
)
986 r
.insert(i
.first
- wxid_range_low
);
990 upload_menu::upload_entry
upload_menu::get_entry(unsigned num
)
992 if(destinations
.count(wxid_range_low
+ num
))
993 return destinations
[wxid_range_low
+ num
];
995 throw std::runtime_error("No such upload target");
998 void upload_menu::delete_entry(unsigned num
)
1001 if(destinations
.count(wxid_range_low
+ num
)) {
1002 Delete(destinations
[wxid_range_low
+ num
].item
);
1003 destinations
.erase(wxid_range_low
+ num
);
1008 void upload_menu::on_select(wxCommandEvent
& e
)
1012 modal_pause_holder hld
;
1015 if(id
== wxid_range_high
) {
1016 f
= new wxeditor_uploadtargets(pwin
, this);
1017 } else if(destinations
.count(id
)) {
1018 f
= new wxeditor_uploaddialog(pwin
, inst
, destinations
[id
]);
1023 } catch(canceled_exception
& e
) {
1026 throw canceled_exception();