1 #include "wui/savegamedeleter.h"
2
3 #include <boost/format.hpp>
4
5 #include "base/i18n.h"
6 #include "base/log.h"
7 #include "io/filesystem/filesystem_exceptions.h"
8 #include "io/filesystem/layered_filesystem.h"
9 #include "logic/filesystem_constants.h"
10 #include "ui_basic/messagebox.h"
11
SavegameDeleter(UI::Panel * parent)12 SavegameDeleter::SavegameDeleter(UI::Panel* parent) : parent_(parent) {
13 }
14
delete_savegames(const std::vector<SavegameData> & to_be_deleted) const15 bool SavegameDeleter::delete_savegames(const std::vector<SavegameData>& to_be_deleted) const {
16 bool do_delete = SDL_GetModState() & KMOD_CTRL;
17 if (!do_delete) {
18 do_delete = show_confirmation_window(to_be_deleted);
19 }
20 if (do_delete) {
21 delete_and_count_failures(to_be_deleted);
22 }
23 return do_delete;
24 }
25
show_confirmation_window(const std::vector<SavegameData> & selections) const26 bool SavegameDeleter::show_confirmation_window(const std::vector<SavegameData>& selections) const {
27 size_t no_selections = selections.size();
28 std::string confirmation_window_header = create_header_for_confirmation_window(no_selections);
29 const std::string message =
30 (boost::format("%s\n%s") % confirmation_window_header % as_filename_list(selections)).str();
31
32 UI::WLMessageBox confirmationBox(
33 parent_->get_parent()->get_parent(),
34 no_selections == 1 ? _("Confirm Deleting File") : _("Confirm Deleting Files"), message,
35 UI::WLMessageBox::MBoxType::kOkCancel);
36 return confirmationBox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk;
37 }
38
39 const std::string
create_header_for_confirmation_window(const size_t no_selections) const40 SavegameDeleter::create_header_for_confirmation_window(const size_t no_selections) const {
41 std::string header =
42 no_selections == 1 ? _("Do you really want to delete this game?") :
43 /** TRANSLATORS: Used with multiple games, 1 game has a separate
44 string. DO NOT omit the placeholder in your translation. */
45 (boost::format(ngettext("Do you really want to delete this %d game?",
46 "Do you really want to delete these %d games?", no_selections)) %
47 no_selections)
48 .str();
49
50 return header;
51 }
52
delete_and_count_failures(const std::vector<SavegameData> & to_be_deleted) const53 void SavegameDeleter::delete_and_count_failures(
54 const std::vector<SavegameData>& to_be_deleted) const {
55 uint32_t number_of_failed_deletes = try_to_delete(to_be_deleted);
56 if (number_of_failed_deletes > 0) {
57 notify_deletion_failed(to_be_deleted, number_of_failed_deletes);
58 }
59 }
60
try_to_delete(const std::vector<SavegameData> & to_be_deleted) const61 uint32_t SavegameDeleter::try_to_delete(const std::vector<SavegameData>& to_be_deleted) const {
62 // Failed deletions aren't a serious problem, we just catch the errors
63 // and keep track to notify the player.
64 uint32_t failed_deletions = 0;
65 for (const auto& delete_me : to_be_deleted) {
66 try {
67 g_fs->fs_unlink(delete_me.filename);
68 } catch (const FileError& e) {
69 log("player-requested file deletion failed: %s", e.what());
70 ++failed_deletions;
71 }
72 }
73 return failed_deletions;
74 }
75
notify_deletion_failed(const std::vector<SavegameData> & to_be_deleted,const uint32_t no_failed) const76 void SavegameDeleter::notify_deletion_failed(const std::vector<SavegameData>& to_be_deleted,
77 const uint32_t no_failed) const {
78 const std::string caption =
79 (no_failed == 1) ? _("Error Deleting File!") : _("Error Deleting Files!");
80 std::string header = create_header_for_deletion_failed_window(to_be_deleted.size(), no_failed);
81 std::string message = (boost::format("%s\n%s") % header % as_filename_list(to_be_deleted)).str();
82
83 UI::WLMessageBox msgBox(
84 parent_->get_parent()->get_parent(), caption, message, UI::WLMessageBox::MBoxType::kOk);
85 msgBox.run<UI::Panel::Returncodes>();
86 }
87
create_header_for_deletion_failed_window(size_t no_to_be_deleted,size_t no_failed) const88 std::string SavegameDeleter::create_header_for_deletion_failed_window(size_t no_to_be_deleted,
89 size_t no_failed) const {
90 if (no_to_be_deleted == 1) {
91 return _("The game could not be deleted.");
92 } else {
93 /** TRANSLATORS: Used with multiple games, 1 game has a separate
94 string. DO NOT omit the placeholder in your translation. */
95 return (boost::format(ngettext(
96 "%d game could not be deleted.", "%d games could not be deleted.", no_failed)) %
97 no_failed)
98 .str();
99 }
100 }
101
ReplayDeleter(UI::Panel * parent)102 ReplayDeleter::ReplayDeleter(UI::Panel* parent) : SavegameDeleter(parent) {
103 }
104
105 const std::string
create_header_for_confirmation_window(const size_t no_selections) const106 ReplayDeleter::create_header_for_confirmation_window(const size_t no_selections) const {
107 std::string header =
108 no_selections == 1 ?
109 _("Do you really want to delete this replay?") :
110 /** TRANSLATORS: Used with multiple replays, 1 replay has a
111 separate string. DO NOT omit the placeholder in your translation. */
112 (boost::format(ngettext("Do you really want to delete this %d replay?",
113 "Do you really want to delete these %d replays?", no_selections)) %
114 no_selections)
115 .str();
116
117 return header;
118 }
119
create_header_for_deletion_failed_window(size_t no_to_be_deleted,size_t no_failed) const120 std::string ReplayDeleter::create_header_for_deletion_failed_window(size_t no_to_be_deleted,
121 size_t no_failed) const {
122 if (no_to_be_deleted == 1) {
123 return _("The replay could not be deleted.");
124 } else {
125 /** TRANSLATORS: Used with multiple replays, 1 replay has a separate
126 string. DO NOT omit the placeholder in your translation. */
127 return (boost::format(ngettext("%d replay could not be deleted.",
128 "%d replays could not be deleted.", no_failed)) %
129 no_failed)
130 .str();
131 }
132 }
133
try_to_delete(const std::vector<SavegameData> & to_be_deleted) const134 uint32_t ReplayDeleter::try_to_delete(const std::vector<SavegameData>& to_be_deleted) const {
135 // Failed deletions aren't a serious problem, we just catch the errors
136 // and keep track to notify the player.
137 uint32_t failed_deletions = 0;
138 bool failed;
139 for (const auto& delete_me : to_be_deleted) {
140 failed = false;
141 const std::string& file_to_be_deleted = delete_me.filename;
142 try {
143 g_fs->fs_unlink(file_to_be_deleted);
144 } catch (const FileError& e) {
145 log("player-requested file deletion failed: %s", e.what());
146 failed = true;
147 }
148
149 try {
150 g_fs->fs_unlink(file_to_be_deleted + kSavegameExtension);
151 // If at least one of the two relevant files of a replay are
152 // successfully deleted then count it as success.
153 // (From the player perspective the replay is gone.)
154 failed = false;
155 // If it was a multiplayer replay, also delete the synchstream. Do
156 // it here, so it's only attempted if replay deletion was successful.
157 if (g_fs->file_exists(file_to_be_deleted + kSyncstreamExtension)) {
158 g_fs->fs_unlink(file_to_be_deleted + kSyncstreamExtension);
159 }
160 } catch (const FileError& e) {
161 log("player-requested file deletion failed: %s", e.what());
162 }
163
164 if (failed) {
165 ++failed_deletions;
166 }
167 }
168 return failed_deletions;
169 }
170