Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/MemStickScreen.cpp
3185 views
1
// Copyright (c) 2013- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#include "android/jni/app-android.h"
21
22
#include "Common/Log.h"
23
#include "Common/UI/UI.h"
24
#include "Common/UI/View.h"
25
#include "Common/UI/ViewGroup.h"
26
27
#include "Common/StringUtils.h"
28
#include "Common/System/System.h"
29
#include "Common/System/Request.h"
30
#include "Common/System/NativeApp.h"
31
#include "Common/System/Display.h"
32
#include "Common/System/OSD.h"
33
#include "Common/Data/Text/I18n.h"
34
#include "Common/Data/Text/Parsers.h"
35
36
#include "Common/File/AndroidStorage.h"
37
#include "Common/File/FileUtil.h"
38
#include "Common/File/Path.h"
39
#include "Common/File/DiskFree.h"
40
41
#include "Common/Thread/ThreadManager.h"
42
43
#include "Core/Config.h"
44
#include "Core/Reporting.h"
45
#include "Core/System.h"
46
#include "Core/Util/GameManager.h"
47
#include "Core/Util/MemStick.h"
48
49
#include "UI/MemStickScreen.h"
50
#include "UI/MainScreen.h"
51
#include "UI/MiscScreens.h"
52
#include "UI/OnScreenDisplay.h"
53
54
static std::string FormatSpaceString(int64_t space) {
55
if (space >= 0) {
56
char buffer[50];
57
NiceSizeFormat(space, buffer, sizeof(buffer));
58
return buffer;
59
} else {
60
return "N/A";
61
}
62
}
63
64
MemStickScreen::MemStickScreen(bool initialSetup)
65
: initialSetup_(initialSetup) {
66
#if PPSSPP_PLATFORM(ANDROID)
67
// Let's only offer the browse-for-folder choice on Android 10 or later.
68
// Earlier versions often don't really have working folder browsers.
69
storageBrowserWorking_ = System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 29;
70
#else
71
// For testing UI only
72
storageBrowserWorking_ = true;
73
#endif
74
75
if (initialSetup_) {
76
// Preselect current choice.
77
if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
78
choice_ = CHOICE_BROWSE_FOLDER;
79
} else {
80
WARN_LOG_REPORT(Log::System, "Scoped storage not enabled - shouldn't be in MemStickScreen at initial setup");
81
choice_ = CHOICE_STORAGE_ROOT;
82
// Shouldn't really be here in initial setup.
83
}
84
} else {
85
// Detect the current choice, so it's preselected in the UI.
86
#if PPSSPP_PLATFORM(UWP)
87
if (g_Config.memStickDirectory == g_Config.internalDataDirectory) {
88
#else
89
if (g_Config.memStickDirectory == Path(g_extFilesDir)) {
90
#endif
91
choice_ = CHOICE_PRIVATE_DIRECTORY;
92
} else if (g_Config.memStickDirectory == Path(g_externalDir)) {
93
choice_ = CHOICE_STORAGE_ROOT;
94
} else if (storageBrowserWorking_) {
95
choice_ = CHOICE_BROWSE_FOLDER;
96
} else {
97
choice_ = CHOICE_SET_MANUAL;
98
}
99
}
100
}
101
102
static void AddExplanation(UI::ViewGroup *viewGroup, MemStickScreen::Choice choice, UI::View *extraView = nullptr) {
103
auto iz = GetI18NCategory(I18NCat::MEMSTICK);
104
using namespace UI;
105
106
int flags = FLAG_WRAP_TEXT;
107
108
UI::ViewGroup *holder = new UI::LinearLayout(ORIENT_VERTICAL);
109
110
UI::ViewGroup *indentHolder = new UI::LinearLayout(ORIENT_HORIZONTAL);
111
indentHolder->Add(new Spacer(20.0));
112
indentHolder->Add(holder);
113
114
viewGroup->Add(indentHolder);
115
116
if (extraView) {
117
holder->Add(extraView);
118
}
119
120
switch (choice) {
121
case MemStickScreen::CHOICE_STORAGE_ROOT:
122
// Old school choice
123
holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true);
124
holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true);
125
holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
126
break;
127
case MemStickScreen::CHOICE_BROWSE_FOLDER:
128
holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true);
129
holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true);
130
#if !PPSSPP_PLATFORM(UWP)
131
holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
132
#endif
133
break;
134
case MemStickScreen::CHOICE_PRIVATE_DIRECTORY:
135
// Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic?
136
holder->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!"), flags, false))->SetBullet(true);
137
holder->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!"), flags, false))->SetBullet(true);
138
#if !PPSSPP_PLATFORM(UWP)
139
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
140
holder->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files"), flags, false))->SetBullet(true);
141
} else {
142
holder->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files"), flags, false))->SetBullet(true);
143
}
144
#endif
145
break;
146
case MemStickScreen::CHOICE_SET_MANUAL:
147
holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true);
148
holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
149
break;
150
default:
151
holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true);
152
break;
153
}
154
}
155
156
void MemStickScreen::CreateViews() {
157
using namespace UI;
158
159
auto di = GetI18NCategory(I18NCat::DIALOG);
160
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
161
162
Margins actionMenuMargins(15, 0, 15, 0);
163
164
root_ = new LinearLayout(ORIENT_HORIZONTAL);
165
166
Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f));
167
ScrollView *mainColumnScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
168
169
ViewGroup *mainColumn = new LinearLayoutList(ORIENT_VERTICAL);
170
mainColumnScroll->Add(mainColumn);
171
172
root_->Add(spacerColumn);
173
root_->Add(mainColumnScroll);
174
175
if (initialSetup_) {
176
mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
177
mainColumn->Add(new TextView(ms->T("Welcome to PPSSPP!"), ALIGN_LEFT, false));
178
}
179
180
mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 18.0f, 0.0f)));
181
182
mainColumn->Add(new TextView(ms->T("MemoryStickDescription", "Choose where to keep PSP data (Memory Stick)"), ALIGN_LEFT, false));
183
mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 18.0f, 0.0f)));
184
185
ViewGroup *subColumns = new LinearLayoutList(ORIENT_HORIZONTAL);
186
mainColumn->Add(subColumns);
187
188
ViewGroup *leftColumn = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
189
subColumns->Add(leftColumn);
190
191
ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(220, FILL_PARENT, actionMenuMargins));
192
subColumns->Add(rightColumnItems);
193
194
// For legacy Android systems, so you can switch back to the old ways if you move to SD or something.
195
// Trying to avoid needing a scroll view, so only showing the explanation for one option at a time.
196
#if !PPSSPP_PLATFORM(UWP)
197
if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
198
leftColumn->Add(new RadioButton(&choice_, CHOICE_STORAGE_ROOT, ms->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
199
if (choice_ == CHOICE_STORAGE_ROOT) {
200
AddExplanation(leftColumn, (MemStickScreen::Choice)choice_);
201
}
202
}
203
#endif
204
205
if (storageBrowserWorking_) {
206
leftColumn->Add(new RadioButton(&choice_, CHOICE_BROWSE_FOLDER, ms->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
207
208
// TODO: Show current folder here if we have one set.
209
}
210
211
#ifdef ANDROID_LEGACY
212
constexpr bool legacy = true;
213
#else
214
constexpr bool legacy = false;
215
#endif
216
217
if (!storageBrowserWorking_ || legacy) {
218
leftColumn->Add(new RadioButton(&choice_, CHOICE_SET_MANUAL, ms->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
219
// TODO: Show current folder here if we have one set.
220
}
221
errorNoticeView_ = leftColumn->Add(new NoticeView(NoticeLevel::WARN, ms->T("Cancelled - try again"), ""));
222
errorNoticeView_->SetVisibility(UI::V_GONE);
223
224
if (choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) {
225
UI::View *extraView = nullptr;
226
if (!g_Config.memStickDirectory.empty()) {
227
extraView = new TextView(StringFromFormat(" %s: %s", ms->T_cstr("Current"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false);
228
}
229
AddExplanation(leftColumn, (MemStickScreen::Choice)choice_, extraView);
230
}
231
232
std::string privateString(ms->T("Use App Private Data"));
233
234
if (initialSetup_) {
235
privateString = StringFromFormat("%s (%s)", ms->T_cstr("Skip for now"), privateString.c_str());
236
}
237
238
leftColumn->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, privateString))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick);
239
if (choice_ == CHOICE_PRIVATE_DIRECTORY) {
240
AddExplanation(leftColumn, (MemStickScreen::Choice)choice_);
241
}
242
243
leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
244
245
std::string_view confirmButtonText;
246
ImageID confirmButtonImage = ImageID::invalid();
247
switch (choice_) {
248
case CHOICE_BROWSE_FOLDER:
249
confirmButtonText = di->T("OK");
250
confirmButtonImage = ImageID("I_FOLDER_OPEN");
251
break;
252
case CHOICE_PRIVATE_DIRECTORY:
253
if (initialSetup_) {
254
confirmButtonText = di->T("Skip");
255
confirmButtonImage = ImageID("I_WARNING");
256
} else {
257
confirmButtonText = di->T("OK");
258
}
259
break;
260
case CHOICE_STORAGE_ROOT:
261
case CHOICE_SET_MANUAL:
262
default:
263
confirmButtonText = di->T("OK");
264
break;
265
}
266
267
rightColumnItems->Add(new UI::Choice(confirmButtonText, confirmButtonImage))->OnClick.Handle<MemStickScreen>(this, &MemStickScreen::OnConfirmClick);
268
rightColumnItems->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f)));
269
270
if (!initialSetup_) {
271
rightColumnItems->Add(new UI::Choice(di->T("Back")))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
272
}
273
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_TV) {
274
rightColumnItems->Add(new UI::Choice(ms->T("WhatsThis", "What's this?")))->OnClick.Handle<MemStickScreen>(this, &MemStickScreen::OnHelp);
275
}
276
277
INFO_LOG(Log::System, "MemStickScreen: initialSetup=%d", (int)initialSetup_);
278
}
279
280
UI::EventReturn MemStickScreen::OnHelp(UI::EventParams &params) {
281
// I'm letting the old redirect handle this one, as the target is within /docs on the website,
282
// and that structure may change a bit.
283
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/guide_storage.html");
284
285
return UI::EVENT_DONE;
286
}
287
288
UI::EventReturn MemStickScreen::OnChoiceClick(UI::EventParams &params) {
289
// Change the confirm button to match the choice,
290
// and change the text that we show.
291
RecreateViews();
292
return UI::EVENT_DONE;
293
}
294
295
296
UI::EventReturn MemStickScreen::OnConfirmClick(UI::EventParams &params) {
297
switch (choice_) {
298
case CHOICE_SET_MANUAL:
299
return SetFolderManually(params);
300
case CHOICE_STORAGE_ROOT:
301
return UseStorageRoot(params);
302
case CHOICE_PRIVATE_DIRECTORY:
303
return UseInternalStorage(params);
304
case CHOICE_BROWSE_FOLDER:
305
return Browse(params);
306
}
307
return UI::EVENT_DONE;
308
}
309
310
UI::EventReturn MemStickScreen::SetFolderManually(UI::EventParams &params) {
311
// The old way, from before scoped storage. Write in the full path.
312
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH)
313
auto sy = GetI18NCategory(I18NCat::SYSTEM);
314
System_InputBoxGetString(GetRequesterToken(), sy->T("Memory Stick Folder"), g_Config.memStickDirectory.ToString(), false, [&](const std::string &value, int) {
315
auto sy = GetI18NCategory(I18NCat::SYSTEM);
316
auto di = GetI18NCategory(I18NCat::DIALOG);
317
318
std::string newPath = value;
319
size_t pos = newPath.find_last_not_of('/');
320
// Gotta have at least something but a /, and also needs to start with a /.
321
if (newPath.empty() || pos == std::string::npos || newPath[0] != '/') {
322
settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr);
323
return;
324
}
325
if (pos != newPath.size() - 1) {
326
newPath = newPath.substr(0, pos + 1);
327
}
328
329
if (newPath.empty()) {
330
// Reuse below message instead of adding yet another string.
331
System_Toast(sy->T("Path does not exist!"));
332
return;
333
}
334
335
Path pendingMemStickFolder(newPath);
336
337
if (!File::Exists(pendingMemStickFolder)) {
338
// Try to fix the path string, apparently some users got used to leaving out the /.
339
if (newPath[0] != '/') {
340
newPath = "/" + newPath;
341
}
342
343
pendingMemStickFolder = Path(newPath);
344
}
345
346
if (!File::Exists(pendingMemStickFolder) && pendingMemStickFolder.Type() == PathType::NATIVE) {
347
// Still no path? Try to automatically fix the case.
348
std::string oldNewPath = newPath;
349
FixPathCase(Path(""), newPath, FixPathCaseBehavior::FPC_FILE_MUST_EXIST);
350
if (oldNewPath != newPath) {
351
NOTICE_LOG(Log::IO, "Fixed path case: %s -> %s", oldNewPath.c_str(), newPath.c_str());
352
pendingMemStickFolder = Path(newPath);
353
} else {
354
NOTICE_LOG(Log::IO, "Failed to fix case of path %s (result: %s)", newPath.c_str(), oldNewPath.c_str());
355
}
356
}
357
358
if (pendingMemStickFolder == g_Config.memStickDirectory) {
359
// Same directory as before - all good. Nothing to do.
360
TriggerFinish(DialogResult::DR_OK);
361
return;
362
}
363
364
if (!File::Exists(pendingMemStickFolder)) {
365
System_Toast(sy->T("Path does not exist!"));
366
return;
367
}
368
369
screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, false));
370
});
371
#endif
372
return UI::EVENT_DONE;
373
}
374
375
UI::EventReturn MemStickScreen::UseInternalStorage(UI::EventParams &params) {
376
#if PPSSPP_PLATFORM(UWP)
377
Path pendingMemStickFolder = g_Config.internalDataDirectory;
378
#else
379
Path pendingMemStickFolder = Path(g_extFilesDir);
380
#endif
381
382
if (initialSetup_) {
383
// There's not gonna be any files here in this case since it's a fresh install.
384
// Let's just accept it and move on. No need to move files either.
385
if (SwitchMemstickFolderTo(pendingMemStickFolder)) {
386
TriggerFinish(DialogResult::DR_OK);
387
} else {
388
// This can't really happen?? Not worth making an error message.
389
ERROR_LOG_REPORT(Log::System, "Could not switch memstick path in setup (internal)");
390
}
391
// Don't have a confirmation dialog that would otherwise do it for us, need to just switch directly to the main screen.
392
screenManager()->switchScreen(new MainScreen());
393
} else if (pendingMemStickFolder != g_Config.memStickDirectory) {
394
// Always ask for confirmation when called from the UI. Likely there's already some data.
395
screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, false));
396
} else {
397
// User chose the same directory it's already in. Let's just bail.
398
TriggerFinish(DialogResult::DR_OK);
399
}
400
return UI::EVENT_DONE;
401
}
402
403
UI::EventReturn MemStickScreen::UseStorageRoot(UI::EventParams &params) {
404
Path pendingMemStickFolder = Path(g_externalDir);
405
406
if (initialSetup_) {
407
// There's not gonna be any files here in this case since it's a fresh install.
408
// Let's just accept it and move on. No need to move files either.
409
if (SwitchMemstickFolderTo(pendingMemStickFolder)) {
410
TriggerFinish(DialogResult::DR_OK);
411
} else {
412
// This can't really happen?? Not worth making an error message.
413
ERROR_LOG_REPORT(Log::System, "Could not switch memstick path in setup");
414
}
415
} else if (pendingMemStickFolder != g_Config.memStickDirectory) {
416
// Always ask for confirmation when called from the UI. Likely there's already some data.
417
screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, false));
418
} else {
419
// User chose the same directory it's already in. Let's just bail.
420
TriggerFinish(DialogResult::DR_OK);
421
}
422
return UI::EVENT_DONE;
423
}
424
425
UI::EventReturn MemStickScreen::Browse(UI::EventParams &params) {
426
auto mm = GetI18NCategory(I18NCat::MAINMENU);
427
System_BrowseForFolder(GetRequesterToken(), mm->T("Choose folder"), g_Config.memStickDirectory, [=](const std::string &value, int) {
428
Path pendingMemStickFolder = Path(value);
429
INFO_LOG(Log::System, "Got folder: '%s' (old: %s)", pendingMemStickFolder.c_str(), g_Config.memStickDirectory.c_str());
430
// Browse finished. Let's pop up the confirmation dialog.
431
if (!pendingMemStickFolder.empty() && pendingMemStickFolder == g_Config.memStickDirectory && File::IsDirectory(pendingMemStickFolder)) {
432
auto di = GetI18NCategory(I18NCat::DIALOG);
433
// Not sure how this could happen, but let's go with it.
434
g_OSD.Show(OSDType::MESSAGE_SUCCESS, di->T("Done!"));
435
done_ = true;
436
return;
437
}
438
errorNoticeView_->SetVisibility(UI::V_GONE);
439
440
screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, initialSetup_));
441
}, [=]() {
442
errorNoticeView_->SetVisibility(UI::V_VISIBLE);
443
});
444
return UI::EVENT_DONE;
445
}
446
447
void MemStickScreen::dialogFinished(const Screen *dialog, DialogResult result) {
448
if (result == DialogResult::DR_OK) {
449
INFO_LOG(Log::System, "Confirmation screen done - moving on.");
450
// There's a screen manager bug if we call TriggerFinish directly.
451
// Can't be bothered right now, so we pick this up in update().
452
done_ = true;
453
}
454
// otherwise, we just keep going.
455
}
456
457
void MemStickScreen::update() {
458
UIDialogScreenWithBackground::update();
459
if (done_) {
460
TriggerFinish(DialogResult::DR_OK);
461
done_ = false;
462
}
463
}
464
465
ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(const Path &newMemstickFolder, bool initialSetup)
466
: newMemstickFolder_(newMemstickFolder), initialSetup_(initialSetup), progressReporter_() {
467
const Path &oldMemstickFolder = g_Config.memStickDirectory;
468
existingFilesInNewFolder_ = FolderSeemsToBeUsed(newMemstickFolder);
469
if (!oldMemstickFolder.empty()) {
470
folderConflict_ = newMemstickFolder != oldMemstickFolder && newMemstickFolder.StartsWithGlobalAndNotEqual(oldMemstickFolder);
471
} else {
472
folderConflict_ = false;
473
}
474
INFO_LOG(Log::System, "Old: '%s'", oldMemstickFolder.c_str());
475
INFO_LOG(Log::System, "New: '%s'", newMemstickFolder.c_str());
476
477
// TODO: If you reinstall the app and start by selecting a subfolder of the PSP folder, we don't detect and warn about that -
478
// we can only warn if there's a known previous folder :(
479
480
if (initialSetup_) {
481
moveData_ = false;
482
}
483
484
newSpaceTask_ = Promise<SpaceResult *>::Spawn(&g_threadManager, [newMemstickFolder]() -> SpaceResult * {
485
int64_t freeSpaceNew;
486
INFO_LOG(Log::System, "Computing free space in '%s'", newMemstickFolder.c_str());
487
free_disk_space(newMemstickFolder, freeSpaceNew);
488
return new SpaceResult{ freeSpaceNew };
489
}, TaskType::IO_BLOCKING, TaskPriority::HIGH);
490
}
491
492
ConfirmMemstickMoveScreen::~ConfirmMemstickMoveScreen() {
493
// We should no longer end up blocking here since the back button is disabled until the tasks are done.
494
if (moveDataTask_) {
495
INFO_LOG(Log::System, "Move Data task still running, blocking on it");
496
moveDataTask_->BlockUntilReady();
497
delete moveDataTask_;
498
}
499
// This we just cancel / leak.
500
if (newSpaceTask_) {
501
newSpaceTask_->Cancel();
502
delete newSpaceTask_;
503
}
504
}
505
506
void ConfirmMemstickMoveScreen::CreateViews() {
507
using namespace UI;
508
auto sy = GetI18NCategory(I18NCat::SYSTEM);
509
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
510
511
root_ = new LinearLayout(ORIENT_HORIZONTAL);
512
513
const Path &oldMemstickFolder = g_Config.memStickDirectory;
514
515
Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f));
516
ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
517
ViewGroup *rightColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0));
518
root_->Add(spacerColumn);
519
root_->Add(leftColumn);
520
root_->Add(rightColumn);
521
522
leftColumn->Add(new TextView(ms->T("Selected PSP Data Folder"), ALIGN_LEFT, false));
523
if (!initialSetup_) {
524
leftColumn->Add(new NoticeView(NoticeLevel::INFO, ms->T("PPSSPP will restart after the change"), ""));
525
}
526
leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false));
527
528
// TODO: Add spinner
529
newFreeSpaceView_ = leftColumn->Add(new TextView(ApplySafeSubstitutions("%1: ...", ms->T("Free space")), ALIGN_LEFT, false));
530
531
if (existingFilesInNewFolder_) {
532
leftColumn->Add(new NoticeView(NoticeLevel::SUCCESS, ms->T("Already contains PSP data"), ""));
533
if (!moveData_) {
534
leftColumn->Add(new NoticeView(NoticeLevel::INFO, ms->T("No data will be changed"), ""));
535
}
536
}
537
538
if (folderConflict_) {
539
leftColumn->Add(new NoticeView(NoticeLevel::WARN, ms->T("The new folder is inside the previous one"), ""));
540
}
541
542
if (!error_.empty()) {
543
leftColumn->Add(new TextView(error_, ALIGN_LEFT, false));
544
}
545
546
if (!oldMemstickFolder.empty()) {
547
rightColumn->Add(new TextView(std::string(ms->T("Current")) + ":", ALIGN_LEFT, false));
548
rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false));
549
}
550
551
if (moveDataTask_) {
552
progressView_ = leftColumn->Add(new TextView(progressReporter_.Format()));
553
} else {
554
progressView_ = nullptr;
555
}
556
557
if (!moveDataTask_) {
558
if (!initialSetup_) {
559
leftColumn->Add(new CheckBox(&moveData_, ms->T("Move Data")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnMoveDataClick);
560
}
561
562
auto di = GetI18NCategory(I18NCat::DIALOG);
563
leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm);
564
leftColumn->Add(new Choice(di->T("Back")))->OnClick.Add([this](UI::EventParams &params) {
565
if (moveDataTask_ && !moveDataTask_->Poll()) {
566
return UI::EVENT_DONE;
567
}
568
return UIScreen::OnBack(params);
569
});
570
}
571
}
572
573
UI::EventReturn ConfirmMemstickMoveScreen::OnMoveDataClick(UI::EventParams &params) {
574
RecreateViews();
575
return UI::EVENT_DONE;
576
}
577
578
void ConfirmMemstickMoveScreen::update() {
579
UIDialogScreenWithBackground::update();
580
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
581
582
if (moveDataTask_) {
583
if (progressView_) {
584
progressView_->SetText(progressReporter_.Format());
585
}
586
587
MoveResult *result = moveDataTask_->Poll();
588
589
if (result) {
590
if (result->success) {
591
progressReporter_.SetProgress(ms->T("Done!"));
592
INFO_LOG(Log::System, "Move data task finished successfully!");
593
// Succeeded!
594
FinishFolderMove();
595
} else {
596
progressReporter_.SetProgress(ms->T("Failed to move some files!"));
597
INFO_LOG(Log::System, "Move data task finished with failures!");
598
// What do we do here? We might be in the middle of a move... Bad.
599
RecreateViews();
600
}
601
delete moveDataTask_;
602
moveDataTask_ = nullptr;
603
}
604
}
605
606
if (newSpaceTask_ && newFreeSpaceView_) {
607
SpaceResult *result = newSpaceTask_->Poll();
608
if (result) {
609
newFreeSpaceView_->SetText(std::string(ms->T("Free space")) + ": " + FormatSpaceString(result->bytesFree));
610
delete newSpaceTask_;
611
newSpaceTask_ = nullptr;
612
}
613
}
614
}
615
616
UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams &params) {
617
// Transfer all the files in /PSP from the original directory.
618
// Should probably be done on a background thread so we can show some UI.
619
// So we probably need another screen for this with a progress bar..
620
// If the directory itself is called PSP, don't go below.
621
622
if (moveData_) {
623
progressReporter_.SetProgress(T(I18NCat::MEMSTICK, "Starting move..."));
624
625
moveDataTask_ = Promise<MoveResult *>::Spawn(&g_threadManager, [&]() -> MoveResult * {
626
Path moveSrc = g_Config.memStickDirectory;
627
Path moveDest = newMemstickFolder_;
628
MoveResult *result = MoveDirectoryContentsSafe(moveSrc, moveDest, progressReporter_);
629
NOTICE_LOG(Log::System, "Move task finished: %d", (int)(result != nullptr));
630
return result;
631
}, TaskType::IO_BLOCKING, TaskPriority::HIGH);
632
633
RecreateViews();
634
} else {
635
FinishFolderMove();
636
}
637
638
return UI::EVENT_DONE;
639
}
640
641
void ConfirmMemstickMoveScreen::FinishFolderMove() {
642
auto ms = GetI18NCategory(I18NCat::MEMSTICK);
643
644
Path oldMemstickFolder = g_Config.memStickDirectory;
645
646
// Successful so far, switch the memstick folder.
647
if (!SwitchMemstickFolderTo(newMemstickFolder_)) {
648
// TODO: More precise errors.
649
error_ = ms->T("That folder doesn't work as a memstick folder.");
650
return;
651
}
652
653
INFO_LOG(Log::System, "Move from '%s' to '%s' complete. Updating config.", oldMemstickFolder.c_str(), newMemstickFolder_.c_str());
654
655
// If the chosen folder already had a config, reload it!
656
g_Config.Load();
657
658
// If the current browser directory is the old memstick folder, drop it.
659
if (g_Config.currentDirectory == oldMemstickFolder) {
660
g_Config.currentDirectory = g_Config.defaultCurrentDirectory;
661
}
662
663
PostLoadConfig();
664
665
if (!initialSetup_) {
666
// We restart the app here, to get the new settings.
667
INFO_LOG(Log::System, "Not initial setup. Restarting!");
668
System_RestartApp("");
669
} else {
670
// This is initial setup, we now switch to the main screen, if we were successful
671
// (which we better have been...)
672
if (g_Config.Save("MemstickPathChanged")) {
673
INFO_LOG(Log::System, "Initial setup succeeded. Switching to main screen!");
674
// TriggerFinish(DialogResult::DR_OK);
675
screenManager()->switchScreen(new MainScreen());
676
} else {
677
INFO_LOG(Log::System, "Initial setup failed.");
678
error_ = ms->T("Failed to save config");
679
RecreateViews();
680
}
681
}
682
}
683
684