mu-init: automatic export labels with --reinit
When re-initializing the store, automatically write the labels to a file in mu's cache, so user can later import them.
This commit is contained in:
@ -24,9 +24,9 @@
|
||||
using namespace Mu;
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view path_key = "path:";
|
||||
constexpr std::string_view message_id_key = "message-id:";
|
||||
constexpr std::string_view labels_key = "labels:";
|
||||
constexpr std::string_view path_key = "path:";
|
||||
constexpr std::string_view message_id_key = "message-id:";
|
||||
constexpr std::string_view labels_key = "labels:";
|
||||
}
|
||||
|
||||
using OutputPair = std::pair<std::ofstream, std::string>;
|
||||
@ -36,14 +36,26 @@ export_output(Option<std::string> path)
|
||||
{
|
||||
const auto now_t{::time({})};
|
||||
const auto now_tm{::localtime(&now_t)};
|
||||
|
||||
const auto now{mu_format("{:%F-%T}", *now_tm)};
|
||||
auto fname = path.value_or(mu_format("mu-export-{}.txt", now));
|
||||
|
||||
// if path is not specified, use a generated file name (in pwd)
|
||||
// if path is specified but ends in '/', use the generated file in that
|
||||
// directory (must exist)
|
||||
// otherwise, use the path.
|
||||
auto fname = [&]() {
|
||||
const auto default_fname{mu_format("mu-export-{}.txt", now)};
|
||||
if (!path || path->empty())
|
||||
return default_fname;
|
||||
else if (path->at(path->length() - 1) == '/')
|
||||
return *path + default_fname;
|
||||
else
|
||||
return *path;
|
||||
}();
|
||||
|
||||
auto output{std::ofstream{fname, std::ios::out}};
|
||||
if (!output.good())
|
||||
return Err(Error{Error::Code::File,
|
||||
"failed pen '{}' for writing", fname});
|
||||
"failed to open '{}' for writing", fname});
|
||||
|
||||
mu_println(output, ";; version:0 @ {}\n", now);
|
||||
|
||||
|
||||
@ -129,11 +129,13 @@ public:
|
||||
std::string line;
|
||||
|
||||
while (std::getline(ss, line)) {
|
||||
if (const auto parts = Mu::split(line, SepaChar2); parts.size() != 2)
|
||||
if (const auto parts =
|
||||
Mu::split(line, SepaChar2); parts.size() != 2)
|
||||
mu_warning("error: '{}'", line);
|
||||
else
|
||||
map.emplace(std::move(parts[0]),
|
||||
static_cast<std::size_t>(g_ascii_strtoll(parts[1].c_str(),{}, 10)));
|
||||
static_cast<std::size_t>(
|
||||
g_ascii_strtoll(parts[1].c_str(),{}, 10)));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@ -148,14 +150,17 @@ class Store;
|
||||
* Export labels to a file
|
||||
*
|
||||
* If path is not specified, use a file in the current directory
|
||||
* If path ends in '/', write file in the path-directory
|
||||
*
|
||||
* @param store a store object
|
||||
* @param query for the message whose labels to export
|
||||
* @param query for the message whose labels to export (empty for "all")
|
||||
* @param path the path or nothing
|
||||
*
|
||||
* @return either the output filename or some error
|
||||
*/
|
||||
Result<std::string> export_labels(const Store& store, const std::string& query="", Option<std::string> path);
|
||||
Result<std::string> export_labels(const Store& store,
|
||||
const std::string& query="",
|
||||
Option<std::string> path={});
|
||||
|
||||
/**
|
||||
* Import labels from a file
|
||||
@ -170,7 +175,8 @@ Result<std::string> export_labels(const Store& store, const std::string& query="
|
||||
*
|
||||
* @return Ok or some error
|
||||
*/
|
||||
Result<void> import_labels(Store&, const std::string& path, bool dry_run, bool quiet, bool verbose);
|
||||
Result<void> import_labels(Store&, const std::string& path, bool dry_run,
|
||||
bool quiet, bool verbose);
|
||||
|
||||
} // namespace Mux
|
||||
#endif /*MU_LABELS_CACHE_HH*/
|
||||
|
||||
@ -127,7 +127,6 @@ struct Store::Private {
|
||||
Result<Store::Id> update_message_unlocked(Message& msg, Store::Id docid);
|
||||
Result<Store::Id> update_message_unlocked(Message& msg, const std::string& old_path);
|
||||
|
||||
|
||||
using PathMessage = std::pair<std::string, Message>;
|
||||
Result<PathMessage> move_message_unlocked(Message&& msg,
|
||||
Option<const std::string&> target_mdir,
|
||||
@ -211,9 +210,29 @@ Store::Private::find_duplicates_unlocked(const Store& store,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
reinit_export_labels(const Store& store)
|
||||
{
|
||||
// slightly hacky way to get the cache-path...
|
||||
const auto cache_path{canonicalize_filename(
|
||||
join_paths(store.path(), "..")) + "/"};
|
||||
|
||||
if (const auto res =
|
||||
Mu::export_labels(store, "", cache_path); !res)
|
||||
throw Mu::Error(Error::Code::CannotReinit,
|
||||
"cannot re-init; "
|
||||
"failed to export labels: {}",
|
||||
res.error().what())
|
||||
.add_hint("see mu-init(1) for details");
|
||||
else {
|
||||
mu_info("exported labels to: {}", *res);
|
||||
mu_println("exported labels to: {}", *res);
|
||||
}
|
||||
}
|
||||
|
||||
Store::Store(const std::string& path, Store::Options opts)
|
||||
: priv_{std::make_unique<Private>(path, none_of(opts & Store::Options::Writable))}
|
||||
: priv_{std::make_unique<Private>(
|
||||
path, none_of(opts & Store::Options::Writable))}
|
||||
{
|
||||
if (none_of(opts & Store::Options::Writable) &&
|
||||
any_of(opts & Store::Options::ReInit))
|
||||
@ -222,12 +241,19 @@ Store::Store(const std::string& path, Store::Options opts)
|
||||
|
||||
const auto s_version{config().get<Config::Id::SchemaVersion>()};
|
||||
if (any_of(opts & Store::Options::ReInit)) {
|
||||
/* don't try to recover from version with an incompatible scheme */
|
||||
|
||||
/* export labels, if necessary (throws if there's an error) */
|
||||
if (!label_map().empty())
|
||||
reinit_export_labels(*this);
|
||||
|
||||
/* don't try to recover from version with an incompatible
|
||||
* scheme */
|
||||
if (s_version < 500)
|
||||
throw Mu::Error(Error::Code::CannotReinit,
|
||||
"old schema ({}) is too old to re-initialize from",
|
||||
s_version).add_hint("Invoke 'mu init' without '--reinit'; "
|
||||
"see mu-init(1) for details");
|
||||
throw Mu::Error(
|
||||
Error::Code::CannotReinit,
|
||||
"old schema ({}) is too old to re-initialize from",
|
||||
s_version).add_hint("Invoke 'mu init' without '--reinit'; "
|
||||
"see mu-init(1) for details");
|
||||
const auto old_root_maildir{root_maildir()};
|
||||
|
||||
MemDb mem_db;
|
||||
|
||||
@ -131,9 +131,6 @@ Mu::remove_directory(const std::string& path)
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string
|
||||
Mu::runtime_path(Mu::RuntimePath path, const std::string& muhome)
|
||||
{
|
||||
|
||||
@ -13,26 +13,26 @@ mu-init - initialize the *mu* message database
|
||||
* DESCRIPTION
|
||||
|
||||
*mu init* is the subcommand for setting up the *mu* message database. After *mu init*
|
||||
has completed, you can run *mu index*
|
||||
has completed, you can run *mu index*.
|
||||
|
||||
* INIT OPTIONS
|
||||
|
||||
** -m, --maildir _maildir_
|
||||
Use _maildir_ as the root-maildir.
|
||||
|
||||
By default, *mu* uses the *MAILDIR* environment; if it is not set, it uses _~/Maildir_
|
||||
if it is an existing directory. If neither of those can be used, the *--maildir*
|
||||
option is required; it must be an absolute path (but ~~/~ expansion is
|
||||
performed).
|
||||
By default, *mu* uses the *MAILDIR* environment to find the root-maildir. If it is
|
||||
not set, it uses _~/Maildir_ if it is an existing directory. If neither of those
|
||||
can be used, the *--maildir* option is required; it must be an absolute path (but
|
||||
~~/~ expansion is performed).
|
||||
|
||||
** --personal-address _email-address-or-regex_
|
||||
** --my-address _email-address-or-regex_ (alias)
|
||||
Specifies that some e-mail address is a personal address. The option can be used
|
||||
Specifies that some e-mail address is a /personal/ address. The option can be used
|
||||
multiple times, to specify all your addresses.
|
||||
|
||||
Any message in which at least one of the contact fields contains such an address
|
||||
is considered a `personal' message; this can then be used for filtering in
|
||||
{{{man-link(mu-find,1)}}}, {{{man-link(mu-cfind,1)}}} and *mu4e*, e.g. to
|
||||
Any message in which at least one of the contact fields matches a personal
|
||||
address, is considered a `personal' message; this can then be used for filtering
|
||||
in {{{man-link(mu-find,1)}}}, {{{man-link(mu-cfind,1)}}} and *mu4e*, e.g. to
|
||||
filter-out mailing list messages.
|
||||
|
||||
_email-address-or-regex_ can be either a plain e-mail address (such as
|
||||
@ -78,6 +78,8 @@ Reinitialize the database from an earlier version; that is, create a new empty
|
||||
database with the existing settings. This cannot be combined with the other *init*
|
||||
options.
|
||||
|
||||
When you have _labels_ defined for messages in your database, *mu* automatically exports those to file for importing later. See *RESTORING LABELS* below.
|
||||
|
||||
#+include: "muhome.inc" :minlevel 2
|
||||
|
||||
* NGRAM SUPPORT
|
||||
@ -93,6 +95,22 @@ variables such as *XAPIAN_CJK_NGRAM* are ignored.
|
||||
|
||||
#+include: "exit-code.inc" :minlevel 1
|
||||
|
||||
* RESTORING LABELS
|
||||
|
||||
When you have any _labels_ defined for your database, *mu* automatically exports
|
||||
those to a file in the *mu* cache directory; you see this in the ~--init~ output.
|
||||
|
||||
#+begin_example
|
||||
$ mu init --reinit
|
||||
exported labels to: /home/user/.cache/mu/mu-export-2025-08-16-13:43:27.txt
|
||||
#+end_example
|
||||
|
||||
You can restore those labels _after_ re-indexing, e.g.,
|
||||
#+begin_example
|
||||
$ mu label import /home/user/.cache/mu/mu-export-2025-08-16-13:43:27.txt
|
||||
#+end_example
|
||||
|
||||
Please see {{{man-link(mu-label,1)}}} for further details.
|
||||
|
||||
* EXAMPLE
|
||||
#+begin_example
|
||||
@ -105,5 +123,6 @@ $ mu init --maildir=~/Maildir --my-address=alice@example.com --my-address=bob@ex
|
||||
|
||||
{{{man-link(mu-index,1)}}},
|
||||
{{{man-link(mu-find,1)}}},
|
||||
{{{man-link(mu-label,1)}}},
|
||||
{{{man-link(mu-cfind,1)}}},
|
||||
{{{man-link(pcre,3)}}}
|
||||
|
||||
@ -71,12 +71,17 @@ The *list* command lists all the labels that are currently in use in the store.
|
||||
* EXPORT OPTIONS
|
||||
|
||||
The *export* command outputs /all/ labels in the store to a file, so you can *import*
|
||||
it later. The command takes a path to a file as its argument.
|
||||
it later. The command takes a path to a file or a directory (ending in '/') as
|
||||
its argument.
|
||||
|
||||
If a file is specified, *mu* writes the export to it.
|
||||
|
||||
If a directory is specified, *mu* writes to a file in that directory. The directory must already exist.
|
||||
|
||||
When neither is specified, *mu* writes to a file in the current directory.
|
||||
|
||||
See *EXPORT FORMAT* below for details about the format.
|
||||
|
||||
If no file is specified, *mu* creates one for you, in the current directory.
|
||||
|
||||
* IMPORT OPTIONS
|
||||
|
||||
The *import* command is for restoring the labels from a file created through
|
||||
|
||||
Reference in New Issue
Block a user