Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Repository class refactoring #95

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

WIP: Repository class refactoring #95

wants to merge 10 commits into from

Conversation

arthurmco
Copy link
Collaborator

@arthurmco arthurmco commented Jan 27, 2025

Refactor the repository class based on @dhilst 's modeling

@arthurmco arthurmco requested a review from dhilst January 27, 2025 14:11
@arthurmco arthurmco self-assigned this Jan 27, 2025
return file;
} catch (Glib::FileError& e) {
throw repository_exception(
std::format("Could not load repository file {} ({})",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmt::format.


void ELRepoFile::read()
{
printf("reading\n");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No...

Comment on lines 113 to 120
for (const auto& r : m_repositories) {
std::print("repo found '{}'\n", r.group);
std::print("\tname: '{}'\n", r.name);
std::print("\tbaseurl: '{}'\n", r.baseURL.value_or("null"));
std::print("\tmetalink: '{}'\n", r.metalink.value_or("null"));
std::print("\tenabled: {}\n", r.enabled);
std::print("\tgpg: (check: {}, key: {})\n", r.gpgcheck, r.gpgkey);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this debug? If yes add #ifndef NDEBUG. Also we are using fmt::format instead and use the LOG_DEBUG directive.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was glibmm test code I forgot to remove.

include/cloysterhpc/services/repo.h Outdated Show resolved Hide resolved
include/cloysterhpc/services/repo.h Outdated Show resolved Hide resolved
m_repositories.push_back(std::move(repo));
}

for (const auto& r : m_repositories) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid using only r as variable name. Usually we use it for well defined counters, like: i, j and k. Or for iterators, like it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only code for toying with glibmm

In real life, this would not be like this

(This comment applies to every printf/fmt::format comment above)

@viniciusferrao
Copy link
Owner

Refactor the repository class based on @dhilst 's modeling

The modeling was done by the three of us. You included!

Comment on lines 61 to 67
/**
* Repository file class
*/
template <class Repo> class RepoFile {
protected:
std::filesystem::path m_path;
std::vector<Repo> m_repositories;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are mixing up "repository" and File class

  • Remove the Repo type parameter
  • Rename RepoFile to File
  • Remove m_repositories member

This class only wraps file I/O, it must not know anything about repositories

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this?

class File {
protected:
    std::filesystem::path m_path;
public:
    RepoFile(const std::filesystem::path& path)
        : m_path(path)
    {}

    virtual void read() {}
    virtual void write() {}
};

(Me including the repositories list there was a mistake... I already removed it)

Copy link
Owner

@viniciusferrao viniciusferrao Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially, there will be an entity named something like "File" on the namespace "cloysterhpc" that will handle basic file operations. Open, close, write, read, chmod. There's a lot of that on glib and we may use them:

https://gnome.pages.gitlab.gnome.org/glibmm/classGio_1_1File.html
https://gnome.pages.gitlab.gnome.org/glibmm/group__FileUtils.html
https://gnome.pages.gitlab.gnome.org/glibmm/group__UriUtils.html

I do believe that GIO will solve almost everything.

Comment on lines 95 to 107
class ELRepoFile : public RepoFile<ELRepo> {
private:
Glib::RefPtr<Glib::KeyFile> loadFile();

public:
ELRepoFile(const std::filesystem::path& path)
: RepoFile(path)
{
}

void read() override;
void write() override;
};
Copy link
Collaborator

@dhilst dhilst Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have an abstract RepoFile which extends the File, adds the m_repositories generic member, and with parse unparse methods, read is super read followed by parse and write is unparse followed by super write

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::filesystem::path is convertible to std::string or const char*. We must use it. It's the right API.

However the file class that be dependent on std::filesystem::path and not the derived one.

Copy link
Collaborator

@dhilst dhilst Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear

  • parse loads data from the file into the m_repositories member
  • unparse loads the m_repositories member into the file

Also, @arthurmco, add overloads to parse and unparse which I/O into istringstream ostringstream instead of directly into the file so we can test without touching the disks. I don't know if this is possible because I don't know the interface of glibmm if it can load/unload keyfiles from strings, can it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is possible; glibmm can load keyhole data from a string.

Comment on lines +53 to +59
class repository_exception : public std::runtime_error {
public:
explicit repository_exception(const std::string& msg)
: std::runtime_error(msg)
{
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viniciusferrao opinions on this? if we'll not be handling exceptions is there any point in creating our own exceptions?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is just a WIP. But not sure. But it is a good practice to specialize the throw: https://isocpp.org/wiki/faq/exceptions#what-to-throw

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a WIP

Comment on lines 90 to 92
auto metalink = (file->has_key(repogroup, "metalink"))
? std::make_optional(file->get_string(repogroup, "metalink"))
: std::nullopt;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optionals are really cool. Add a free function for reading an optional from keyfiles

Copy link
Owner

@viniciusferrao viniciusferrao Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm an idiot. I have too much difficulty reading ternary operators. That's a technical debt in my mind.

The std::nullopt should be inside a std::make_optional? I don't know.

Copy link
Collaborator

@dhilst dhilst Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is okay, you can read this like this if (file->has_key(repogroup, "metalink")) { metalink = std::make_optional(file->get_string(repogroup, "metalink")) } else { metalink = std::nullopt; }. metalink is an optional set to the metalink property if it exists in the file, or set to nullopt if not. This is pretty general, we'll want to use std::optional to represent optional values in the conf file, so I'm suggesting that we create a function for that

inline std::optional<std::string> get_optional_string(
  const KeyFile& fil,
  const std::string& group,
  const std::string& key)
{
  if (fil->has_key(group, key)) {
    return fil->get_string(group, key);
  }
  return std::nullopt;
}

Copy link
Owner

@viniciusferrao viniciusferrao Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optional is got with the .value() accessor, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but glib keyfile is not using optional here, I guess the interface is older than std::optional

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but glib keyfile is not using optional here, I guess the interface is older than std::optional

Yes it is. But we are talking about the same issue? That our API shuould provide access with std::optional and inside the class we encapsulate that? If that's the case yes I do agree.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying for us to extend glibmm file interface and add this as a member function?

Comment on lines 81 to 86
std::vector<ELRepo> ELRepoFile::parse(const std::stringstream& ss)
{
m_file = Glib::KeyFile::create();
m_file->load_from_data(ss.str().c_str());
return this->parseData();
}
Copy link
Collaborator

@dhilst dhilst Jan 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, now

  • Add m_repositories member as a vector of ELRepo with a getter
  • The getter should load the data into the member if the member is empty and return at the end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside of ELRepo?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside ELRepoFile

struct ELRepo {
std::string group;
std::string name;
std::optional<std::string> baseURL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base_url

Comment on lines 118 to 119
void unparse( const std::vector<ELRepo>& repositories );
void unparse( const std::vector<ELRepo>& repositories, std::stringstream& ss );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void unparse( const std::vector<ELRepo>& repositories );
void unparse( const std::vector<ELRepo>& repositories, std::stringstream& ss );
void unparse(const std::vector<ELRepo>& repositories);
void unparse(const std::vector<ELRepo>& repositories, std::stringstream& ss);

Comment on lines 95 to 100
auto get_optional_string
= [this](auto group, auto key) -> std::optional<Glib::ustring> {
return m_file->has_key(group, key)
? std::make_optional(m_file->get_string(group, key))
: std::nullopt;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this captures this, anyway, move it to functions.cpp

Copy link
Collaborator

@dhilst dhilst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API looks good, there are a few points

  • Make sure to remove the prints
  • Rename the ELReo to ELCloneRepo
  • Remove the PR from draft

Copy link
Owner

@viniciusferrao viniciusferrao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue that I've observed. The virtual classes should be on its own files. This needs to be fixed.


std::vector<ELRepo> ELRepoFile::parse()
{
this->read();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need "this"?


if (name.empty()) {
throw repository_exception(std::format(
"Could not load repo name from repo '{}' at file {}",
"Could not load repo name from repo '{}' at m_file {}",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more than a DEBUG message for the developer than to the user. For the user it should be something more friendly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you suggest?

@@ -164,6 +166,9 @@ std::string findAndReplace(const std::string_view& source,
*/
void copyFile(std::filesystem::path source, std::filesystem::path destination);

std::optional<Glib::ustring> readKeyfileString(Glib::RefPtr<Glib::KeyFile> file,
const std::string_view& group, const std::string_view& key);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std:string_view is already a pointer to a string. It should not be passed as const ref.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that. I thought it was pointer+length. It will be fixed

/**
* Repository file class
*
* This class should parse the repository data
*/
class ELRepoFile : GenericFile {
private:
private:
Glib::RefPtr<Glib::KeyFile> m_file;

std::vector<ELRepo> parseData();
void unparseData(const std::vector<ELRepo>&);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the variable name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name is not needed in the declaration and the context is obvious, but I will fixed because of conventions

@@ -351,4 +351,12 @@ void copyFile(std::filesystem::path source, std::filesystem::path destination)
}
}

std::optional<Glib::ustring> readKeyfileString(Glib::RefPtr<Glib::KeyFile> file,
const std::string_view& group, const std::string_view& key)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be const ref.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arthurmco arthurmco requested a review from dhilst February 4, 2025 13:44
@arthurmco arthurmco marked this pull request as ready for review February 4, 2025 13:45
Copy link

sonarqubecloud bot commented Feb 4, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants