Mercurial > hg > orthanc-wsi
changeset 581:921f79004d81 annotations
tracking active learners vs instructors
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Thu, 03 Sep 2026 15:33:50 +0200 |
| parents | be0aa2c9d0b1 |
| children | 0b9af8de016f |
| files | ViewerPlugin/Annotations/AnnotationsRestApi.cpp ViewerPlugin/Annotations/AnnotationsWorkspace.cpp ViewerPlugin/Annotations/AnnotationsWorkspace.h ViewerPlugin/Annotations/IAuthenticatedUser.cpp ViewerPlugin/Annotations/IAuthenticatedUser.h ViewerPlugin/ViewerConfiguration.h |
| diffstat | 6 files changed, 217 insertions(+), 83 deletions(-) [+] |
line wrap: on
line diff
--- a/ViewerPlugin/Annotations/AnnotationsRestApi.cpp Thu Sep 03 14:49:29 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsRestApi.cpp Thu Sep 03 15:33:50 2026 +0200 @@ -48,7 +48,7 @@ std::unique_ptr<IAuthenticatedUser> user_; Json::Value body_; std::unique_ptr<CachedAnnotationsWorkspace> workspace_; - IAuthenticatedUser::ProjectRole role_; + ProjectRole role_; public: explicit AnnotationsCommandContext(const OrthancPluginHttpRequest* request) @@ -73,8 +73,8 @@ role_ = user_->GetRoleInProject(projectId); - if (role_ != IAuthenticatedUser::ProjectRole_Instructor && - role_ != IAuthenticatedUser::ProjectRole_Learner) + if (role_ != ProjectRole_Instructor && + role_ != ProjectRole_Learner) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ForbiddenAccess, "User \"" + user_->Format() + "\" is not instructor or learner of project \"" + projectId + "\""); @@ -97,11 +97,6 @@ return workspace_->GetContent(); } - IAuthenticatedUser::ProjectRole GetUserRoleInProject() const - { - return role_; - } - std::string GetBodyString(const char* field) const { return Orthanc::SerializationToolbox::ReadString(body_, field); @@ -118,6 +113,16 @@ return body_[field]; } } + + AnnotationsWorkspace::UserReader* CreateUserReader() const + { + return new AnnotationsWorkspace::UserReader(workspace_->GetContent(), GetUser().GetAnnotatingId(), role_); + } + + AnnotationsWorkspace::UserWriter* CreateUserWriter() + { + return new AnnotationsWorkspace::UserWriter(workspace_->GetContent(), GetUser().GetAnnotatingId(), role_); + } }; @@ -175,8 +180,8 @@ Json::Value answer; { - AnnotationsWorkspace::UserReader reader(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - reader.ListLayers(answer); + std::unique_ptr<AnnotationsWorkspace::UserReader> reader(context.CreateUserReader()); + reader->ListLayers(answer); } ViewerToolbox::AnswerJson(output, answer); @@ -195,8 +200,8 @@ Json::Value answer; { - AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - writer.CreateUserLayer(answer); + std::unique_ptr<AnnotationsWorkspace::UserWriter> writer(context.CreateUserWriter()); + writer->CreateUserLayer(answer); } ViewerToolbox::AnswerJson(output, answer); @@ -215,8 +220,8 @@ UserLayer updated(context.GetBodyField("layer")); { - AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - writer.UpdateUserLayer(updated); + std::unique_ptr<AnnotationsWorkspace::UserWriter> writer(context.CreateUserWriter()); + writer->UpdateUserLayer(updated); } ViewerToolbox::AnswerEmpty(output); @@ -235,8 +240,8 @@ const std::string layerId = context.GetBodyString(KEY_LAYER_ID); { - AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - writer.DeleteUserLayer(layerId); + std::unique_ptr<AnnotationsWorkspace::UserWriter> writer(context.CreateUserWriter()); + writer->DeleteUserLayer(layerId); } ViewerToolbox::AnswerEmpty(output); @@ -377,8 +382,8 @@ Json::Value answer; { - AnnotationsWorkspace::UserReader reader(context.GetWorkspace(), user); - reader.ListLayersSharedWith(answer, context.GetUser().GetAnnotatingId()); + std::unique_ptr<AnnotationsWorkspace::UserReader> reader(context.CreateUserReader()); + reader->ListLayersSharedWith(answer, context.GetUser().GetAnnotatingId()); } ViewerToolbox::AnswerJson(output, answer); @@ -402,8 +407,8 @@ const std::string layerId = context.GetBodyString("layer"); { - AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - writer.ImportLayer(author, layerId); + std::unique_ptr<AnnotationsWorkspace::UserWriter> writer(context.CreateUserWriter()); + writer->ImportLayer(author, layerId); } ViewerToolbox::AnswerEmpty(output); @@ -426,8 +431,8 @@ const std::string layerId = context.GetBodyString("layer"); { - AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - writer.RemoveImportedLayer(layerId); + std::unique_ptr<AnnotationsWorkspace::UserWriter> writer(context.CreateUserWriter()); + writer->RemoveImportedLayer(layerId); } ViewerToolbox::AnswerEmpty(output); @@ -446,8 +451,8 @@ ImportedLayer updated(context.GetBodyField("layer")); { - AnnotationsWorkspace::UserWriter writer(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - writer.UpdateImportedLayer(updated); + std::unique_ptr<AnnotationsWorkspace::UserWriter> writer(context.CreateUserWriter()); + writer->UpdateImportedLayer(updated); } ViewerToolbox::AnswerEmpty(output); @@ -485,8 +490,8 @@ std::set<std::string> layerIds; { - AnnotationsWorkspace::UserReader reader(context.GetWorkspace(), context.GetUser().GetAnnotatingId()); - reader.ListImportedLayers(authors, layerIds); + std::unique_ptr<AnnotationsWorkspace::UserReader> reader(context.CreateUserReader()); + reader->ListImportedLayers(authors, layerIds); } Json::Value importedFeatures = Json::arrayValue;
--- a/ViewerPlugin/Annotations/AnnotationsWorkspace.cpp Thu Sep 03 14:49:29 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsWorkspace.cpp Thu Sep 03 15:33:50 2026 +0200 @@ -31,7 +31,8 @@ #include <boost/regex.hpp> -static const char* const KEY_ACTIVE_USERS = "active-users"; +static const char* const KEY_ACTIVE_INSTRUCTORS = "active-instructors"; +static const char* const KEY_ACTIVE_LEARNERS = "active-learners"; namespace OrthancWSI @@ -39,7 +40,40 @@ class AnnotationsWorkspace::PersistentInfo : public ISerializable { private: - std::set<UserId> activeUsers_; + // An active user is always of type "standard" + std::set<UserId> activeInstructors_; + std::set<UserId> activeLearners_; + + + static void ParseActiveUsers(std::set<UserId>& target, + const Json::Value& serialized) + { + if (!serialized.isArray()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + target.clear(); + + for (Json::Value::ArrayIndex i = 0; i < serialized.size(); i++) + { + target.insert(UserId(serialized[i])); + } + } + + + static void SerializeActiveUsers(Json::Value& serialized, + const std::set<UserId>& source) + { + serialized = Json::arrayValue; + + for (std::set<UserId>::const_iterator it = source.begin(); it != source.end(); ++it) + { + Json::Value user; + it->Serialize(user); + serialized.append(user); + } + } public: PersistentInfo() @@ -49,54 +83,137 @@ explicit PersistentInfo(const Json::Value& serialized) { - const Json::Value& users = serialized[KEY_ACTIVE_USERS]; - - if (!users.isArray()) + if (!serialized.isObject() || + !serialized.isMember(KEY_ACTIVE_INSTRUCTORS) || + !serialized.isMember(KEY_ACTIVE_LEARNERS)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } - for (Json::Value::ArrayIndex i = 0; i < users.size(); i++) - { - activeUsers_.insert(UserId(users[i])); - } + ParseActiveUsers(activeInstructors_, serialized[KEY_ACTIVE_INSTRUCTORS]); + ParseActiveUsers(activeLearners_, serialized[KEY_ACTIVE_LEARNERS]); } - // Return "true" iff. the user was not already tagged as active - bool AddActiveUser(const UserId& user) + // Return "true" iff. the user was not already tagged as active or + // if the user has changed their role in the project (from learner + // to instructor, or from instructor to learner) + bool AddActiveUser(const UserId& user, + ProjectRole role) { - if (activeUsers_.find(user) == activeUsers_.end()) + switch (role) { - activeUsers_.insert(user); - return true; + case ProjectRole_Instructor: + if (activeInstructors_.find(user) == activeInstructors_.end()) + { + activeLearners_.erase(user); // Accomodate with change in the role + activeInstructors_.insert(user); + return true; + } + else + { + return false; + } + + case ProjectRole_Learner: + if (activeLearners_.find(user) == activeLearners_.end()) + { + activeInstructors_.erase(user); // Accomodate with change in the role + activeLearners_.insert(user); + return true; + } + else + { + return false; + } + + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - else - { - return false; - } - } - - - const std::set<UserId>& GetActiveUsers() const - { - return activeUsers_; } virtual void Serialize(Json::Value& serialized) const ORTHANC_OVERRIDE { - Json::Value users = Json::arrayValue; - for (std::set<UserId>::const_iterator it = activeUsers_.begin(); it != activeUsers_.end(); ++it) + serialized = Json::objectValue; + SerializeActiveUsers(serialized[KEY_ACTIVE_INSTRUCTORS], activeInstructors_); + SerializeActiveUsers(serialized[KEY_ACTIVE_LEARNERS], activeLearners_); + } + + + class ActiveUsersIterator : public boost::noncopyable + { + private: + std::set<UserId>::const_iterator instructorsIterator_; + std::set<UserId>::const_iterator instructorsEnd_; + std::set<UserId>::const_iterator learnersIterator_; + std::set<UserId>::const_iterator learnersEnd_; + + public: + explicit ActiveUsersIterator(const PersistentInfo& that) : + instructorsIterator_(that.activeInstructors_.begin()), + instructorsEnd_(that.activeInstructors_.end()), + learnersIterator_(that.activeLearners_.begin()), + learnersEnd_(that.activeLearners_.end()) { - Json::Value user; - it->Serialize(user); - users.append(user); + } + + bool IsDone() const + { + return (instructorsIterator_ == instructorsEnd_ && + learnersIterator_ == learnersEnd_); } - serialized = Json::objectValue; - serialized[KEY_ACTIVE_USERS] = users; - } + const UserId& GetUser() const + { + if (instructorsIterator_ != instructorsEnd_) + { + return *instructorsIterator_; + } + else if (learnersIterator_ != learnersEnd_) + { + return *learnersIterator_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + ProjectRole GetRole() const + { + if (instructorsIterator_ != instructorsEnd_) + { + return ProjectRole_Instructor; + } + else if (learnersIterator_ != learnersEnd_) + { + return ProjectRole_Learner; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + void Next() + { + if (instructorsIterator_ != instructorsEnd_) + { + ++instructorsIterator_; + } + else if (learnersIterator_ != learnersEnd_) + { + ++learnersIterator_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + }; }; @@ -129,10 +246,10 @@ { persistentInfo_.reset(new PersistentInfo(info)); - for (std::set<UserId>::const_iterator it = persistentInfo_->GetActiveUsers().begin(); - it != persistentInfo_->GetActiveUsers().end(); ++it) + PersistentInfo::ActiveUsersIterator it(*persistentInfo_); + while (!it.IsDone()) { - Load(*it); + Load(it.GetUser()); } } else @@ -162,13 +279,13 @@ target.clear(); const boost::regex re(query); - const std::set<UserId>& activeUsers = persistentInfo_->GetActiveUsers(); - for (std::set<UserId>::const_iterator it = activeUsers.begin(); it != activeUsers.end(); ++it) + PersistentInfo::ActiveUsersIterator it(*persistentInfo_); + while (!it.IsDone()) { - if (boost::regex_search(it->GetName(), re)) + if (boost::regex_search(it.GetUser().GetName(), re)) { - target.insert(*it); + target.insert(it.GetUser()); } } } @@ -210,10 +327,12 @@ AnnotationsWorkspace::UserReader::UserReader(AnnotationsWorkspace& that, - const UserId& userId) : + const UserId& userId, + ProjectRole userRole) : lock_(that.mutex_), that_(that), - userId_(userId) + userId_(userId), + userRole_(userRole) { Content::const_iterator found = that.content_.find(userId); @@ -318,14 +437,17 @@ AnnotationsWorkspace::UserWriter::UserWriter(AnnotationsWorkspace& that, - const UserId& userId) : + const UserId& userId, + ProjectRole userRole) : lock_(that.mutex_), that_(that), - userId_(userId) + userId_(userId), + userRole_(userRole) { - if (that.persistentInfo_->AddActiveUser(userId_)) + if (that.persistentInfo_->AddActiveUser(userId_, userRole)) { - // Only update the key-value store if this is the first time we meet this user + // Only update the key-value store if this is the first time we + // meet this user or if their role has changed in the project ISerializable::SetKeyValueStore(that.id_.GetInfoKey(), *that.persistentInfo_); }
--- a/ViewerPlugin/Annotations/AnnotationsWorkspace.h Thu Sep 03 14:49:29 2026 +0200 +++ b/ViewerPlugin/Annotations/AnnotationsWorkspace.h Thu Sep 03 15:33:50 2026 +0200 @@ -23,6 +23,7 @@ #pragma once +#include "../ViewerConfiguration.h" #include "AnnotationsWorkspaceId.h" #include "ProjectInformation.h" #include "UserAnnotationsSettings.h" @@ -82,10 +83,12 @@ AnnotationsWorkspace& that_; UserId userId_; const UserAnnotationsSettings* userSettings_; + ProjectRole userRole_; public: UserReader(AnnotationsWorkspace& that, - const UserId& userId); + const UserId& userId, + ProjectRole userRole); bool IsValid() const { @@ -109,12 +112,14 @@ AnnotationsWorkspace& that_; UserId userId_; UserAnnotationsSettings* userSettings_; + ProjectRole userRole_; void Commit(); public: UserWriter(AnnotationsWorkspace& that, - const UserId& userId); + const UserId& userId, + ProjectRole userRole); void CreateUserLayer(Json::Value& answer);
--- a/ViewerPlugin/Annotations/IAuthenticatedUser.cpp Thu Sep 03 14:49:29 2026 +0200 +++ b/ViewerPlugin/Annotations/IAuthenticatedUser.cpp Thu Sep 03 15:33:50 2026 +0200 @@ -237,8 +237,7 @@ if (!tokens.empty() && !tokens[0].empty()) { - //return new GenericStandardUser(IAuthenticatedUser::ProjectRole_Instructor, tokens[0]); - return new GenericStandardUser(IAuthenticatedUser::ProjectRole_Learner, tokens[0]); + return new GenericStandardUser(ProjectRole_Instructor, tokens[0]); } } } @@ -265,7 +264,8 @@ } else { - return new GenericStandardUser(IAuthenticatedUser::ProjectRole_Instructor, request->headersValues[i]); + return new GenericStandardUser(ProjectRole_Instructor, request->headersValues[i]); + //return new GenericStandardUser(ProjectRole_Learner, request->headersValues[i]); } } }
--- a/ViewerPlugin/Annotations/IAuthenticatedUser.h Thu Sep 03 14:49:29 2026 +0200 +++ b/ViewerPlugin/Annotations/IAuthenticatedUser.h Thu Sep 03 15:33:50 2026 +0200 @@ -23,6 +23,7 @@ #pragma once +#include "../ViewerConfiguration.h" #include "UserId.h" #include <orthanc/OrthancCPlugin.h> @@ -35,13 +36,6 @@ class IAuthenticatedUser : public boost::noncopyable { public: - enum ProjectRole - { - ProjectRole_Instructor, - ProjectRole_Learner, - ProjectRole_Guest - }; - virtual ~IAuthenticatedUser() { }
--- a/ViewerPlugin/ViewerConfiguration.h Thu Sep 03 14:49:29 2026 +0200 +++ b/ViewerPlugin/ViewerConfiguration.h Thu Sep 03 15:33:50 2026 +0200 @@ -37,6 +37,14 @@ }; + enum ProjectRole + { + ProjectRole_Instructor, + ProjectRole_Learner, + ProjectRole_Guest + }; + + class ViewerConfiguration { private:
