1 #include "cmdhelp/project.hpp"
2 #include "core/command.hpp"
3 #include "core/controller.hpp"
4 #include "core/dispatch.hpp"
5 #include "core/emustatus.hpp"
6 #include "core/instance.hpp"
7 #include "core/inthread.hpp"
8 #include "core/mainloop.hpp"
9 #include "core/memorywatch.hpp"
10 #include "core/messages.hpp"
11 #include "core/moviedata.hpp"
12 #include "core/moviefile.hpp"
13 #include "core/project.hpp"
14 #include "core/queue.hpp"
15 #include "core/window.hpp"
16 #include "library/directory.hpp"
17 #include "library/minmax.hpp"
18 #include "library/string.hpp"
22 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
27 void do_flush_slotinfo();
31 void concatenate(std::vector
<char>& data
, const std::vector
<char>& app
)
33 size_t dsize
= data
.size();
34 data
.resize(dsize
+ app
.size());
35 std::copy(app
.begin(), app
.end(), data
.begin() + dsize
);
38 std::vector
<char> base_decode(const std::string
& str
)
41 size_t len
= str
.length();
43 uint32_t a
, b
, c
, d
, e
, v
;
44 for(i
= 0; i
+ 5 <= len
; i
+= 5) {
50 v
= 52200625 * a
+ 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
56 a
= b
= c
= d
= e
= 0;
57 if(i
+ 0 < len
) e
= str
[len
- 1] - 33;
58 if(i
+ 1 < len
) d
= str
[len
- 2] - 33;
59 if(i
+ 2 < len
) c
= str
[len
- 3] - 33;
60 if(i
+ 3 < len
) b
= str
[len
- 4] - 33;
61 v
= 614125 * b
+ 7225 * c
+ 85 * d
+ e
;
62 if(i
+ 1 < len
) r
.push_back(v
);
63 if(i
+ 2 < len
) r
.push_back(v
>> 8);
64 if(i
+ 3 < len
) r
.push_back(v
>> 16);
68 template<int x
> void blockcode(std::ostringstream
& s
, const std::vector
<char>& data
, size_t& ptr
,
71 uint32_t a
= 0, b
= 0, c
= 0, d
= 0, v
;
72 if(x
>= 4) a
= static_cast<uint8_t>(data
[ptr
++]);
73 if(x
>= 3) b
= static_cast<uint8_t>(data
[ptr
++]);
74 if(x
>= 2) c
= static_cast<uint8_t>(data
[ptr
++]);
75 if(x
>= 1) d
= static_cast<uint8_t>(data
[ptr
++]);
76 v
= 16777216 * d
+ 65536 * c
+ 256 * b
+ a
;
78 if(x
>= 4) s
<< static_cast<char>(v
/ 52200625 % 85 + 33);
79 if(x
>= 3) s
<< static_cast<char>(v
/ 614125 % 85 + 33);
80 if(x
>= 2) s
<< static_cast<char>(v
/ 7225 % 85 + 33);
81 if(x
>= 1) s
<< static_cast<char>(v
/ 85 % 85 + 33);
82 if(x
>= 1) s
<< static_cast<char>(v
% 85 + 33);
86 std::pair
<std::string
, size_t> base_encode(const std::vector
<char>& data
, size_t ptr
, size_t chars
)
89 while(chars
>= 5 && ptr
+ 4 <= data
.size())
90 blockcode
<4>(s
, data
, ptr
, chars
);
91 if(chars
>= 4 && ptr
+ 3 <= data
.size())
92 blockcode
<3>(s
, data
, ptr
, chars
);
93 if(chars
>= 3 && ptr
+ 2 <= data
.size())
94 blockcode
<2>(s
, data
, ptr
, chars
);
95 if(chars
>= 2 && ptr
+ 1 <= data
.size())
96 blockcode
<1>(s
, data
, ptr
, chars
);
97 return std::make_pair(s
.str(), ptr
);
99 std::string
eq_escape(const std::string
& str
)
101 std::ostringstream s
;
102 size_t len
= str
.length();
103 for(size_t i
= 0; i
< len
; i
++) {
106 else if(str
[i
] == '=')
114 std::string
eq_unescape(const std::string
& str
)
116 std::ostringstream s
;
117 size_t len
= str
.length();
119 for(size_t i
= 0; i
< len
; i
++) {
136 void save_binary(std::ostream
& s
, const std::string
& key
, const std::vector
<char>& value
)
139 while(ptr
< value
.size()) {
140 auto v
= base_encode(value
, ptr
, 60);
141 s
<< key
<< "=" << v
.first
<< std::endl
;
146 void fill_stub_movie(struct moviefile
& m
, struct project_info
& p
, struct core_type
& coretype
)
148 //Create a dummy movie.
149 m
.lazy_project_create
= false;
150 m
.start_paused
= true;
151 m
.movie_rtc_second
= p
.movie_rtc_second
;
152 m
.movie_rtc_subsecond
= p
.movie_rtc_subsecond
;
155 m
.anchor_savestate
= p
.anchor_savestate
;
156 m
.movie_sram
= p
.movie_sram
;
157 m
.authors
= p
.authors
;
158 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
159 m
.romimg_sha256
[i
] = p
.romimg_sha256
[i
];
160 m
.romxml_sha256
[i
] = p
.romxml_sha256
[i
];
161 m
.namehint
[i
] = p
.namehint
[i
];
163 m
.projectid
= p
.projectid
;
164 m
.coreversion
= p
.coreversion
;
165 m
.gamename
= p
.gamename
;
166 m
.settings
= p
.settings
;
167 auto ctrldata
= coretype
.controllerconfig(m
.settings
);
168 portctrl::type_set
& ports
= portctrl::type_set::make(ctrldata
.ports
, ctrldata
.portindex());
169 m
.create_default_branch(ports
);
171 m
.gametype
= &coretype
.lookup_sysregion(p
.gametype
);
172 } catch(std::bad_alloc
& e
) {
174 } catch(std::exception
& e
) {
175 throw std::runtime_error("Illegal game type '" + p
.gametype
+ "'");
179 std::string
project_getname(const std::string
& id
)
181 std::string file
= get_config_path() + "/" + id
+ ".prj";
182 std::ifstream
f(file
);
184 throw std::runtime_error("Can't open project file");
186 std::getline(f
, name
);
188 throw std::runtime_error("Can't read project name");
193 project_state::project_state(voice_commentary
& _commentary
, memwatch_set
& _mwatch
, command::group
& _command
,
194 controller_state
& _controls
, settingvar::cache
& _setcache
, button_mapping
& _buttons
,
195 emulator_dispatch
& _edispatch
, input_queue
& _iqueue
, loaded_rom
& _rom
, status_updater
& _supdater
)
196 : commentary(_commentary
), mwatch(_mwatch
), command(_command
), controls(_controls
), setcache(_setcache
),
197 buttons(_buttons
), edispatch(_edispatch
), iqueue(_iqueue
), rom(_rom
), supdater(_supdater
),
198 branch_ls(command
, STUBS::branch_ls
, [this]() { this->do_branch_ls(); }),
199 branch_mk(command
, STUBS::branch_mk
, [this](const std::string
& a
) { this->do_branch_mk(a
); }),
200 branch_rm(command
, STUBS::branch_rm
, [this](const std::string
& a
) { this->do_branch_rm(a
); }),
201 branch_set(command
, STUBS::branch_set
, [this](const std::string
& a
) { this->do_branch_set(a
); }),
202 branch_rp(command
, STUBS::branch_rp
, [this](const std::string
& a
) { this->do_branch_rp(a
); }),
203 branch_mv(command
, STUBS::branch_mv
, [this](const std::string
& a
) { this->do_branch_mv(a
); })
205 active_project
= NULL
;
208 project_state::~project_state()
212 project_info
& project_state::load(const std::string
& id
)
214 std::string file
= get_config_path() + "/" + id
+ ".prj";
215 std::ifstream
f(file
);
217 throw std::runtime_error("Can't open project file");
218 project_info
& pi
= *new project_info(edispatch
);
220 pi
.movie_rtc_second
= 1000000000;
221 pi
.movie_rtc_subsecond
= 0;
222 pi
.active_branch
= 0;
225 //First line is always project name.
226 std::getline(f
, pi
.name
);
227 if(!f
|| pi
.name
== "") {
229 throw std::runtime_error("Can't read project file");
233 std::getline(f
, tmp
);
235 if(r
= regex("rom=(.+)", tmp
))
237 else if(r
= regex("last-save=(.+)", tmp
))
239 else if(r
= regex("directory=(.+)", tmp
))
241 else if(r
= regex("prefix=(.+)", tmp
))
243 else if(r
= regex("luascript=(.+)", tmp
))
244 pi
.luascripts
.push_back(r
[1]);
245 else if(r
= regex("gametype=(.+)", tmp
))
247 else if(r
= regex("coreversion=(.+)", tmp
))
248 pi
.coreversion
= r
[1];
249 else if(r
= regex("gamename=(.+)", tmp
))
251 else if(r
= regex("projectid=(.+)", tmp
))
253 else if(r
= regex("projectid=([0-9]+):([0-9]+)", tmp
)) {
254 pi
.movie_rtc_second
= parse_value
<int64_t>(r
[1]);
255 pi
.movie_rtc_subsecond
= parse_value
<int64_t>(r
[2]);
256 } else if(r
= regex("author=(.*)\\|(.*)", tmp
))
257 pi
.authors
.push_back(std::make_pair(r
[1], r
[2]));
258 else if(r
= regex("author=(.+)", tmp
))
259 pi
.authors
.push_back(std::make_pair(r
[1], ""));
260 else if(r
= regex("romsha=([0-9a-f]+)", tmp
))
261 pi
.romimg_sha256
[0] = r
[1];
262 else if(r
= regex("slotsha([a-z])=([0-9a-f]+)", tmp
))
263 pi
.romimg_sha256
[r
[1][0] - 96] = r
[2];
264 else if(r
= regex("romxml=([0-9a-f]+)", tmp
))
265 pi
.romxml_sha256
[0] = r
[1];
266 else if(r
= regex("slotxml([a-z])=([0-9a-f]+)", tmp
))
267 pi
.romxml_sha256
[r
[1][0] - 96] = r
[2];
268 else if(r
= regex("romhint=(.*)", tmp
))
269 pi
.namehint
[0] = r
[1];
270 else if(r
= regex("slothint([a-z])=(.*)", tmp
))
271 pi
.namehint
[r
[1][0] - 96] = r
[2];
272 else if(r
= regex("romrom=(.*)", tmp
))
274 else if(r
= regex("slotrom([a-z])=(.*)", tmp
))
275 pi
.roms
[r
[1][0] - 96] = r
[2];
276 else if(r
= regex("setting.([^=]+)=(.*)", tmp
))
277 pi
.settings
[r
[1]] = r
[2];
278 else if(r
= regex("watch.([^=]+)=(.*)", tmp
))
279 pi
.watches
[eq_unescape(r
[1])] = r
[2];
280 else if(r
= regex("sram.([^=]+)=(.*)", tmp
))
281 concatenate(pi
.movie_sram
[r
[1]], base_decode(r
[2]));
282 else if(r
= regex("macro.([^=]+)=(.*)", tmp
))
284 pi
.macros
[r
[1]] = JSON::node(r
[2]);
285 } catch(std::exception
& e
) {
286 messages
<< "Unable to load macro '" << r
[1] << "': " << e
.what() << std::endl
;
288 else if(r
= regex("anchor=(.*)", tmp
))
289 concatenate(pi
.anchor_savestate
, base_decode(r
[1]));
290 else if(r
= regex("time=([0-9]+):([0-9]+)", tmp
)) {
291 pi
.movie_rtc_second
= parse_value
<int64_t>(r
[1]);
292 pi
.movie_rtc_subsecond
= parse_value
<int64_t>(r
[2]);
293 } else if(r
= regex("branch([1-9][0-9]*)parent=([0-9]+)", tmp
)) {
294 uint64_t bid
= parse_value
<int64_t>(r
[1]);
295 uint64_t pbid
= parse_value
<int64_t>(r
[2]);
296 if(!pi
.branches
.count(bid
))
297 pi
.branches
[bid
].name
= "(Unnamed branch)";
298 pi
.branches
[bid
].pbid
= pbid
;
299 } else if(r
= regex("branch([1-9][0-9]*)name=(.*)", tmp
)) {
300 uint64_t bid
= parse_value
<int64_t>(r
[1]);
301 if(!pi
.branches
.count(bid
))
302 pi
.branches
[bid
].pbid
= 0;
303 pi
.branches
[bid
].name
= r
[2];
304 } else if(r
= regex("branchcurrent=([0-9]+)", tmp
)) {
305 pi
.active_branch
= parse_value
<int64_t>(r
[1]);
306 } else if(r
= regex("branchnext=([0-9]+)", tmp
)) {
307 pi
.next_branch
= parse_value
<int64_t>(r
[1]);
310 for(auto& i
: pi
.branches
) {
311 uint64_t j
= i
.first
;
314 j
= pi
.branches
[j
].pbid
;
318 messages
<< "Warning: Cyclic slot branch dependency, reparenting '" <<
319 pi
.branches
[m
].name
<< "' to be child of root..." << std::endl
;
320 pi
.branches
[j
].pbid
= 0;
325 if(pi
.active_branch
&& !pi
.branches
.count(pi
.active_branch
)) {
326 messages
<< "Warning: Current slot branch does not exist, using root..." << std::endl
;
327 pi
.active_branch
= 0;
332 project_info
* project_state::get()
334 return active_project
;
337 bool project_state::set(project_info
* p
, bool current
)
341 commentary
.unload_collection();
343 edispatch
.core_change();
344 edispatch
.branch_change();
349 moviefile
* newmovie
= NULL
;
350 bool switched
= false;
351 std::set
<core_sysregion
*> sysregs
;
357 sysregs
= core_sysregion::find_matching(p
->gametype
);
359 throw std::runtime_error("No core supports '" + p
->gametype
+ "'");
361 //First, try to load the ROM and the last movie file into RAM...
363 newrom
= loaded_rom(p
->rom
, p
->coreversion
);
365 core_type
* ctype
= NULL
;
366 for(auto i
: sysregs
) {
367 ctype
= &i
->get_type();
368 if(ctype
->get_core_identifier() == p
->coreversion
)
371 newrom
= loaded_rom(p
->roms
, ctype
->get_core_identifier(), ctype
->get_iname(), "");
373 if(newrom
.get_core_identifier() != p
->coreversion
) {
374 messages
<< "Warning: Can't find matching core, using " << newrom
.get_core_identifier()
377 if(p
->last_save
!= "")
379 newmovie
= new moviefile(p
->last_save
, newrom
.get_internal_rom_type());
380 } catch(std::exception
& e
) {
381 messages
<< "Warning: Can't load last save: " << e
.what() << std::endl
;
382 newmovie
= new moviefile();
383 fill_stub_movie(*newmovie
, *p
, newrom
.get_internal_rom_type());
386 newmovie
= new moviefile();
387 fill_stub_movie(*newmovie
, *p
, newrom
.get_internal_rom_type());
389 //Okay, loaded, load into core.
390 newrom
.load(p
->settings
, p
->movie_rtc_second
, p
->movie_rtc_subsecond
);
392 do_load_state(*newmovie
, LOAD_STATE_DEFAULT
, used
);
396 //Calculate union of old and new.
397 std::set
<std::string
> _watches
= mwatch
.enumerate();
398 for(auto i
: p
->watches
) _watches
.insert(i
.first
);
400 for(auto i
: _watches
)
402 if(p
->watches
.count(i
))
403 mwatch
.set(i
, p
->watches
[i
]);
406 } catch(std::exception
& e
) {
407 messages
<< "Can't set/clear watch '" << i
<< "': " << e
.what() << std::endl
;
409 commentary
.load_collection(p
->directory
+ "/" + p
->prefix
+ ".lsvs");
410 command
.invoke("reset-lua");
411 for(auto i
: p
->luascripts
)
412 command
.invoke("run-lua " + i
);
413 buttons
.load(controls
, *active_project
);
414 } catch(std::exception
& e
) {
415 if(newmovie
&& !used
)
417 platform::error_message(std::string("Can't switch projects: ") + e
.what());
418 messages
<< "Can't switch projects: " << e
.what() << std::endl
;
423 edispatch
.core_change();
424 edispatch
.branch_change();
429 std::map
<std::string
, std::string
> project_state::enumerate()
431 std::set
<std::string
> projects
;
432 std::map
<std::string
, std::string
> projects2
;
434 projects
= directory::enumerate(get_config_path(), ".*\\.prj");
435 for(auto i
: projects
) {
439 split
= id
.find_last_of("\\/");
441 split
= id
.find_last_of("/");
443 if(split
< id
.length())
444 id
= id
.substr(split
+ 1);
445 id
= id
.substr(0, id
.length() - 4);
447 projects2
[id
] = project_getname(id
);
449 messages
<< "Failed to load name for ID '" << id
<< "'" << std::endl
;
455 std::string
project_state::moviepath()
458 return active_project
->directory
;
460 return setcache
.get("moviepath");
463 std::string
project_state::otherpath()
466 return active_project
->directory
;
471 std::string
project_state::savestate_ext()
473 return active_project
? "lss" : "lsmv";
476 void project_state::copy_watches(project_info
& p
)
478 for(auto i
: mwatch
.enumerate()) {
480 p
.watches
[i
] = mwatch
.get_string(i
);
481 } catch(std::exception
& e
) {
482 messages
<< "Can't read memory watch '" << i
<< "': " << e
.what() << std::endl
;
487 void project_state::copy_macros(project_info
& p
, controller_state
& s
)
489 for(auto i
: s
.enumerate_macro())
490 p
.macros
[i
] = s
.get_macro(i
).serialize();
493 project_info::project_info(emulator_dispatch
& _dispatch
)
494 : edispatch(_dispatch
)
498 uint64_t project_info::get_parent_branch(uint64_t bid
)
502 if(!branches
.count(bid
))
503 throw std::runtime_error("Invalid branch ID");
504 return branches
[bid
].pbid
;
507 void project_info::set_current_branch(uint64_t bid
)
509 if(bid
&& !branches
.count(bid
))
510 throw std::runtime_error("Invalid branch ID");
512 edispatch
.branch_change();
513 messages
<< "Set current slot branch to " << get_branch_string() << std::endl
;
516 const std::string
& project_info::get_branch_name(uint64_t bid
)
518 static std::string rootname
= "(root)";
521 if(!branches
.count(bid
))
522 throw std::runtime_error("Invalid branch ID");
523 return branches
[bid
].name
;
526 void project_info::set_branch_name(uint64_t bid
, const std::string
& name
)
529 throw std::runtime_error("Root branch name can't be set");
530 if(!branches
.count(bid
))
531 throw std::runtime_error("Invalid branch ID");
532 branches
[bid
].name
= name
;
533 edispatch
.branch_change();
536 void project_info::set_parent_branch(uint64_t bid
, uint64_t pbid
)
539 throw std::runtime_error("Root branch never has parent");
540 if(!branches
.count(bid
))
541 throw std::runtime_error("Invalid branch ID");
542 if(pbid
&& !branches
.count(pbid
))
543 throw std::runtime_error("Invalid parent branch ID");
545 throw std::runtime_error("Branch can't be its own parent");
546 for(auto& i
: branches
) {
547 uint64_t j
= i
.first
;
549 j
= (j
== bid
) ? pbid
: branches
[j
].pbid
;
551 throw std::runtime_error("Reparenting would create a circular dependency");
554 branches
[bid
].pbid
= pbid
;
555 edispatch
.branch_change();
558 std::set
<uint64_t> project_info::branch_children(uint64_t bid
)
560 if(bid
&& !branches
.count(bid
))
561 throw std::runtime_error("Invalid branch ID");
562 std::set
<uint64_t> r
;
563 for(auto& i
: branches
)
564 if(i
.second
.pbid
== bid
)
569 uint64_t project_info::create_branch(uint64_t pbid
, const std::string
& name
)
571 if(pbid
&& !branches
.count(pbid
))
572 throw std::runtime_error("Invalid parent branch ID");
573 uint64_t assign_bid
= next_branch
;
574 uint64_t last_bid
= (branches
.empty() ? 1 : branches
.rbegin()->first
+ 1);
575 assign_bid
= max(assign_bid
, last_bid
);
576 branches
[assign_bid
].name
= name
;
577 branches
[assign_bid
].pbid
= pbid
;
578 next_branch
= assign_bid
+ 1;
579 edispatch
.branch_change();
583 void project_info::delete_branch(uint64_t bid
)
586 throw std::runtime_error("Root branch can not be deleted");
587 if(bid
== active_branch
)
588 throw std::runtime_error("Current branch can't be deleted");
589 if(!branches
.count(bid
))
590 throw std::runtime_error("Invalid branch ID");
591 for(auto& i
: branches
)
592 if(i
.second
.pbid
== bid
)
593 throw std::runtime_error("Can't delete branch with children");
595 edispatch
.branch_change();
598 std::string
project_info::get_branch_string()
601 uint64_t j
= active_branch
;
606 r
= get_branch_name(j
);
608 r
= get_branch_name(j
) + "→" + r
;
609 j
= get_parent_branch(j
);
614 void project_info::flush()
616 std::string file
= get_config_path() + "/" + id
+ ".prj";
617 std::string tmpfile
= get_config_path() + "/" + id
+ ".prj.tmp";
618 std::string bakfile
= get_config_path() + "/" + id
+ ".prj.bak";
619 std::ofstream
f(tmpfile
);
621 throw std::runtime_error("Can't write project file");
624 throw std::runtime_error("Can't write project file");
627 std::ifstream
f2(file
);
629 std::ofstream
f3(bakfile
);
631 throw std::runtime_error("Can't backup project file");
634 std::getline(f2
, tmp
);
635 f3
<< tmp
<< std::endl
;
640 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
641 if(MoveFileEx(tmpfile
.c_str(), file
.c_str(), MOVEFILE_REPLACE_EXISTING
) < 0)
643 if(rename(tmpfile
.c_str(), file
.c_str()) < 0)
645 throw std::runtime_error("Can't replace project file");
648 void project_info::write(std::ostream
& s
)
650 s
<< name
<< std::endl
;
651 s
<< "rom=" << rom
<< std::endl
;
653 s
<< "last-save=" << last_save
<< std::endl
;
654 s
<< "directory=" << directory
<< std::endl
;
655 s
<< "prefix=" << prefix
<< std::endl
;
656 for(auto i
: luascripts
)
657 s
<< "luascript=" << i
<< std::endl
;
658 s
<< "gametype=" << gametype
<< std::endl
;
659 s
<< "coreversion=" << coreversion
<< std::endl
;
661 s
<< "gamename=" << gamename
<< std::endl
;
662 s
<< "projectid=" << projectid
<< std::endl
;
663 s
<< "time=" << movie_rtc_second
<< ":" << movie_rtc_subsecond
<< std::endl
;
664 for(auto i
: authors
)
665 s
<< "author=" << i
.first
<< "|" << i
.second
<< std::endl
;
666 for(unsigned i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
667 if(romimg_sha256
[i
] != "") {
669 s
<< "slotsha" << static_cast<char>(96 + i
) << "=" << romimg_sha256
[i
] << std::endl
;
671 s
<< "romsha=" << romimg_sha256
[i
] << std::endl
;
673 if(romxml_sha256
[i
] != "") {
675 s
<< "slotxml" << static_cast<char>(96 + i
) << "=" << romxml_sha256
[i
] << std::endl
;
677 s
<< "romxml=" << romxml_sha256
[i
] << std::endl
;
679 if(namehint
[i
] != "") {
681 s
<< "slothint" << static_cast<char>(96 + i
) << "=" << namehint
[i
] << std::endl
;
683 s
<< "romhint=" << namehint
[i
] << std::endl
;
687 s
<< "slotrom" << static_cast<char>(96 + i
) << "=" << roms
[i
] << std::endl
;
689 s
<< "romrom=" << roms
[i
] << std::endl
;
692 for(auto i
: settings
)
693 s
<< "setting." << i
.first
<< "=" << i
.second
<< std::endl
;
694 for(auto i
: watches
)
695 s
<< "watch." << eq_escape(i
.first
) << "=" << i
.second
<< std::endl
;
697 s
<< "macro." + i
.first
<< "=" << i
.second
.serialize() << std::endl
;
698 for(auto i
: movie_sram
)
699 save_binary(s
, "sram." + i
.first
, i
.second
);
700 if(anchor_savestate
.size())
701 save_binary(s
, "anchor", anchor_savestate
);
702 for(auto& i
: branches
) {
703 s
<< "branch" << i
.first
<< "parent=" << i
.second
.pbid
<< std::endl
;
704 s
<< "branch" << i
.first
<< "name=" << i
.second
.name
<< std::endl
;
706 s
<< "branchcurrent=" << active_branch
<< std::endl
;
707 s
<< "branchnext=" << next_branch
<< std::endl
;
710 void project_state::do_branch_mk(const std::string
& args
)
712 regex_results r
= regex("([0-9]+)[ \t]+(.*)", args
);
714 messages
<< "Syntax: create-branch <parentid> <name>" << std::endl
;
719 uint64_t pbid
= parse_value
<uint64_t>(r
[1]);
721 throw std::runtime_error("Not in project context");
722 uint64_t bid
= prj
->create_branch(pbid
, r
[2]);
723 messages
<< "Created branch #" << bid
<< std::endl
;
725 } catch(std::exception
& e
) {
726 messages
<< "Can't create new branch: " << e
.what() << std::endl
;
730 void project_state::do_branch_rm(const std::string
& args
)
732 regex_results r
= regex("([0-9]+)[ \t]*", args
);
734 messages
<< "Syntax: delete-branch <id>" << std::endl
;
739 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
741 throw std::runtime_error("Not in project context");
742 prj
->delete_branch(bid
);
743 messages
<< "Deleted branch #" << bid
<< std::endl
;
745 } catch(std::exception
& e
) {
746 messages
<< "Can't delete branch: " << e
.what() << std::endl
;
750 void project_state::do_branch_set(const std::string
& args
)
752 regex_results r
= regex("([0-9]+)[ \t]*", args
);
754 messages
<< "Syntax: set-branch <id>" << std::endl
;
759 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
761 throw std::runtime_error("Not in project context");
762 prj
->set_current_branch(bid
);
763 messages
<< "Set current branch to #" << bid
<< std::endl
;
766 } catch(std::exception
& e
) {
767 messages
<< "Can't set branch: " << e
.what() << std::endl
;
771 void project_state::do_branch_rp(const std::string
& args
)
773 regex_results r
= regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args
);
775 messages
<< "Syntax: reparent-branch <id> <newpid>" << std::endl
;
780 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
781 uint64_t pbid
= parse_value
<uint64_t>(r
[2]);
783 throw std::runtime_error("Not in project context");
784 prj
->set_parent_branch(bid
, pbid
);
785 messages
<< "Reparented branch #" << bid
<< std::endl
;
788 } catch(std::exception
& e
) {
789 messages
<< "Can't reparent branch: " << e
.what() << std::endl
;
793 void project_state::do_branch_mv(const std::string
& args
)
795 regex_results r
= regex("([0-9]+)[ \t]+(.*)", args
);
797 messages
<< "Syntax: rename-branch <id> <name>" << std::endl
;
802 uint64_t bid
= parse_value
<uint64_t>(r
[1]);
804 throw std::runtime_error("Not in project context");
805 prj
->set_branch_name(bid
, r
[2]);
806 messages
<< "Renamed branch #" << bid
<< std::endl
;
809 } catch(std::exception
& e
) {
810 messages
<< "Can't rename branch: " << e
.what() << std::endl
;
814 void project_state::do_branch_ls()
816 std::set
<unsigned> dset
;
817 recursive_list_branch(0, dset
, 0, false);
820 void project_state::recursive_list_branch(uint64_t bid
, std::set
<unsigned>& dset
, unsigned depth
, bool last_of
)
824 messages
<< "Not in project context." << std::endl
;
827 std::set
<uint64_t> children
= prj
->branch_children(bid
);
829 for(unsigned i
= 0; i
+ 1 < depth
; i
++)
830 prefix
+= (dset
.count(i
) ? "\u2502" : " ");
831 prefix
+= (dset
.count(depth
- 1) ? (last_of
? "\u2514" : "\u251c") : " ");
832 if(last_of
) dset
.erase(depth
- 1);
834 << ((bid
== prj
->get_current_branch()) ? "*" : "")
835 << bid
<< ":" << prj
->get_branch_name(bid
) << std::endl
;
838 for(auto i
: children
) {
839 bool last
= (++c
== children
.size());
840 recursive_list_branch(i
, dset
, depth
+ 1, last
);