aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp201
-rw-r--r--src/plugins/Matrix.cpp85
2 files changed, 243 insertions, 43 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index f3e83f6..590eb86 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -3116,7 +3116,7 @@ namespace QuickMedia {
pinned_tab.body = std::make_unique<Body>(this, loading_icon);
pinned_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE;
pinned_tab.body->thumbnail_mask_shader = &circle_mask_shader;
- pinned_tab.body->attach_side = AttachSide::BOTTOM;
+ pinned_tab.body->attach_side = AttachSide::TOP;
//pinned_tab.body->line_separator_color = sf::Color::Transparent;
pinned_tab.text = sf::Text("Pinned messages", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size);
tabs.push_back(std::move(pinned_tab));
@@ -3130,8 +3130,18 @@ namespace QuickMedia {
messages_tab.text = sf::Text("Messages", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size);
tabs.push_back(std::move(messages_tab));
+ // ChatTab users_tab;
+ // users_tab.body = std::make_unique<Body>(this, loading_icon);
+ // users_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE;
+ // users_tab.body->thumbnail_mask_shader = &circle_mask_shader;
+ // users_tab.body->attach_side = AttachSide::TOP;
+ // //users_tab.body->line_separator_color = sf::Color::Transparent;
+ // users_tab.text = sf::Text("Users", *FontLoader::get_font(FontLoader::FontType::LATIN), tab_text_size);
+ // tabs.push_back(std::move(users_tab));
+
const int PINNED_TAB_INDEX = 0;
const int MESSAGES_TAB_INDEX = 1;
+ //const int USERS_TAB_INDEX = 2;
int selected_tab = MESSAGES_TAB_INDEX;
bool is_window_focused = window.hasFocus();
@@ -3258,6 +3268,7 @@ namespace QuickMedia {
bool empty_before = tabs[PINNED_TAB_INDEX].body->items.empty();
int selected_before = tabs[PINNED_TAB_INDEX].body->get_selected_item();
auto prev_pinned_body_items = tabs[PINNED_TAB_INDEX].body->items;
+ tabs[PINNED_TAB_INDEX].body->clear_items();
// TODO: Add message to rooms messages when there are new pinned events
for(const std::string &event : pinned_events.value()) {
@@ -3409,23 +3420,53 @@ namespace QuickMedia {
AsyncTask<Messages> previous_messages_future;
+ enum class FetchMessageType {
+ MESSAGE,
+ USER_UPDATE,
+ ROOM_USERS
+ };
+
+ struct FetchMessageResult {
+ FetchMessageType type;
+ std::shared_ptr<Message> message;
+ };
+
//const int num_fetch_message_threads = 4;
- AsyncTask<std::shared_ptr<Message>> fetch_message_future;
- RoomData *fetch_future_room = nullptr;
+ AsyncTask<FetchMessageResult> fetch_message_future;
+ Message *fetch_message = nullptr;
BodyItem *fetch_body_item = nullptr;
int fetch_message_tab = -1;
// TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads.
// TODO: Cancel when going to another room?
- tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, &current_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) {
+ tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, &current_room, &fetch_message_future, &tabs, &fetch_message, &find_body_item_by_event_id, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) {
if(fetch_message_future.valid() || !current_room)
return;
PinnedEventData *event_data = static_cast<PinnedEventData*>(body_item->userdata);
- if(!event_data || event_data->status != FetchStatus::NONE)
+ if(!event_data)
+ return;
+
+#if 0
+ if(event_data->message->user->resolve_state == UserResolveState::NOT_RESOLVED) {
+ fetch_message = event_data->message;
+ event_data->message->user->resolve_state = UserResolveState::RESOLVING;
+ std::string user_id = event_data->message->user->user_id;
+ fetch_message_future = [this, &current_room, user_id]() {
+ matrix->update_user_with_latest_state(current_room, user_id);
+ return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr};
+ };
+ return;
+ } else if(event_data->message->user->resolve_state == UserResolveState::RESOLVING) {
+ return;
+ }
+#endif
+
+ if(event_data->status != FetchStatus::NONE)
return;
- // Check if we already have the referenced message as a body item in the messages list, so we dont create a new one
+ // Check if we already have the referenced message as a body item in the messages list, so we dont create a new one.
+ // TODO: Optimize from linear search to hash map
auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), event_data->event_id);
if(related_body_item) {
*body_item = *related_body_item;
@@ -3436,28 +3477,44 @@ namespace QuickMedia {
}
std::string message_event_id = event_data->event_id;
- fetch_future_room = current_room;
fetch_body_item = body_item;
event_data->status = FetchStatus::LOADING;
fetch_message_tab = PINNED_TAB_INDEX;
// TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive?
- fetch_message_future = [this, &fetch_future_room, message_event_id]() {
- return matrix->get_message_by_id(fetch_future_room, message_event_id);
+ fetch_message_future = [this, &current_room, message_event_id]() {
+ return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)};
};
};
// TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads.
// TODO: Cancel when going to another room?
- tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, &current_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) {
+ tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, &current_room, &fetch_message_future, &tabs, &fetch_message, &find_body_item_by_event_id, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) {
if(fetch_message_future.valid() || !current_room)
return;
Message *message = static_cast<Message*>(body_item->userdata);
assert(message);
+
+#if 0
+ if(message->user->resolve_state == UserResolveState::NOT_RESOLVED) {
+ fetch_message = message;
+ message->user->resolve_state = UserResolveState::RESOLVING;
+ std::string user_id = message->user->user_id;
+ fetch_message_future = [this, &current_room, user_id]() {
+ matrix->update_user_with_latest_state(current_room, user_id);
+ return FetchMessageResult{FetchMessageType::USER_UPDATE, nullptr};
+ };
+ return;
+ } else if(message->user->resolve_state == UserResolveState::RESOLVING) {
+ return;
+ }
+#endif
+
if(message->related_event_id.empty() || body_item->embedded_item_status != FetchStatus::NONE)
return;
- // Check if we already have the referenced message as a body item, so we dont create a new one
+ // Check if we already have the referenced message as a body item, so we dont create a new one.
+ // TODO: Optimize from linear search to hash map
auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id);
if(related_body_item) {
body_item->embedded_item = related_body_item;
@@ -3466,13 +3523,12 @@ namespace QuickMedia {
}
std::string message_event_id = message->related_event_id;
- fetch_future_room = current_room;
fetch_body_item = body_item;
body_item->embedded_item_status = FetchStatus::LOADING;
fetch_message_tab = MESSAGES_TAB_INDEX;
// TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive?
- fetch_message_future = [this, &fetch_future_room, message_event_id]() {
- return matrix->get_message_by_id(fetch_future_room, message_event_id);
+ fetch_message_future = [this, &current_room, message_event_id]() {
+ return FetchMessageResult{FetchMessageType::MESSAGE, matrix->get_message_by_id(current_room, message_event_id)};
};
};
@@ -3491,7 +3547,7 @@ namespace QuickMedia {
sf::Vertex gradient_points[4];
double gradient_inc = 0;
- tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y);
+ //tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y);
bool fetched_enough_messages = false;
bool initial_prev_messages_fetch = true;
@@ -3648,6 +3704,81 @@ namespace QuickMedia {
return false;
};
+ auto update_pinned_messages_author = [&tabs, &current_room](const std::shared_ptr<UserInfo> &user) {
+ fprintf(stderr, "updated pinned messages author for user: %s\n", user->user_id.c_str());
+ std::string user_display_name = current_room->get_user_display_name(user);
+ std::string user_avatar_url = current_room->get_user_avatar_url(user);
+
+ for(auto &pinned_body_item : tabs[PINNED_TAB_INDEX].body->items) {
+ Message *message = static_cast<PinnedEventData*>(pinned_body_item->userdata)->message;
+ // Its fine if we dont set it now. When the message is fetches, it will have updated user info since its fetched later
+ if(!message || message->user != user)
+ continue;
+
+ pinned_body_item->set_author(user_display_name);
+ if(!is_visual_media_message_type(message->type)) {
+ pinned_body_item->thumbnail_url = user_avatar_url;
+ pinned_body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that
+ pinned_body_item->thumbnail_size = sf::Vector2i(32, 32);
+ }
+ }
+ };
+
+ auto update_messages_author = [&tabs, &current_room](const std::shared_ptr<UserInfo> &user) {
+ fprintf(stderr, "updated messages author for user: %s\n", user->user_id.c_str());
+ std::string user_display_name = current_room->get_user_display_name(user);
+ std::string user_avatar_url = current_room->get_user_avatar_url(user);
+
+ for(auto &message_body_items : tabs[MESSAGES_TAB_INDEX].body->items) {
+ Message *message = static_cast<Message*>(message_body_items->userdata);
+ if(message->user != user)
+ continue;
+
+ message_body_items->set_author(user_display_name);
+ if(!is_visual_media_message_type(message->type)) {
+ message_body_items->thumbnail_url = user_avatar_url;
+ message_body_items->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that
+ message_body_items->thumbnail_size = sf::Vector2i(32, 32);
+ }
+ }
+ };
+
+ // TODO: Optimize
+ auto update_pinned_messages_authors = [&tabs, &current_room]() {
+ fprintf(stderr, "updated pinned messages author for all users\n");
+ for(auto &pinned_body_item : tabs[PINNED_TAB_INDEX].body->items) {
+ Message *message = static_cast<PinnedEventData*>(pinned_body_item->userdata)->message;
+ // Its fine if we dont set it now. When the message is fetches, it will have updated user info since its fetched later
+ if(!message)
+ continue;
+
+ pinned_body_item->set_author(current_room->get_user_display_name(message->user));
+ if(!is_visual_media_message_type(message->type)) {
+ pinned_body_item->thumbnail_url = current_room->get_user_avatar_url(message->user);
+ pinned_body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that
+ pinned_body_item->thumbnail_size = sf::Vector2i(32, 32);
+ }
+ }
+ };
+
+ // TODO: Optimize
+ auto update_messages_authors = [&tabs, &current_room]() {
+ fprintf(stderr, "updated messages author for all users\n");
+ for(auto &message_body_items : tabs[MESSAGES_TAB_INDEX].body->items) {
+ Message *message = static_cast<Message*>(message_body_items->userdata);
+ message_body_items->set_author(current_room->get_user_display_name(message->user));
+ if(!is_visual_media_message_type(message->type)) {
+ message_body_items->thumbnail_url = current_room->get_user_avatar_url(message->user);
+ message_body_items->thumbnail_mask_type = ThumbnailMaskType::CIRCLE;
+ // if construct is not configured to use ImageMagic then it wont give thumbnails of size 32x32 even when requested and the spec says that the server SHOULD do that
+ message_body_items->thumbnail_size = sf::Vector2i(32, 32);
+ }
+ }
+ };
+
auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &post_thread, &tabs]() {
set_read_marker_future.cancel();
fetch_message_future.cancel();
@@ -3674,6 +3805,19 @@ namespace QuickMedia {
//tabs.clear();
};
+ // TODO: Remove this once synapse bug has been resolved where /sync does not include user info for new messages when using message filter that limits number of messages for initial sync,
+ // and then only call this when viewing the users tab for the first time.
+ if(current_room->users_fetched) {
+ //TODO BLABLA
+ //update_
+ } else {
+ // TODO: Race condition? maybe use matrix /members instead which has a since parameter to make the members list match current sync
+ fetch_message_future = [this, &current_room]() {
+ matrix->update_room_users(current_room);
+ return FetchMessageResult{FetchMessageType::ROOM_USERS, nullptr};
+ };
+ }
+
float tab_shade_height = 0.0f;
bool frame_skip_text_entry = false;
@@ -4108,31 +4252,38 @@ namespace QuickMedia {
// XXX: Hack to scroll up while keeping the selected item (usually the last one) visible
if(move_to_bottom) {
tabs[MESSAGES_TAB_INDEX].body->select_last_item();
- tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y);
+ //tabs[MESSAGES_TAB_INDEX].body->set_page_scroll(window_size.y);
}
}
fetch_more_previous_messages_if_needed();
}
if(fetch_message_future.ready()) {
- std::shared_ptr<Message> message = fetch_message_future.get();
- fprintf(stderr, "Finished fetching message: %s\n", message ? message->event_id.c_str() : "(null)");
- // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again
- if(fetch_future_room == current_room) {
+ FetchMessageResult fetch_message_result = fetch_message_future.get();
+ if(fetch_message_result.type == FetchMessageType::ROOM_USERS) {
+ current_room->users_fetched = true;
+ update_pinned_messages_authors();
+ update_messages_authors();
+ } else if(fetch_message_result.type == FetchMessageType::USER_UPDATE) {
+ update_pinned_messages_author(fetch_message->user);
+ update_messages_author(fetch_message->user);
+ fetch_message = nullptr;
+ } else if(fetch_message_result.type == FetchMessageType::MESSAGE) {
+ fprintf(stderr, "Finished fetching message: %s\n", fetch_message_result.message ? fetch_message_result.message->event_id.c_str() : "(null)");
if(fetch_message_tab == PINNED_TAB_INDEX) {
PinnedEventData *event_data = static_cast<PinnedEventData*>(fetch_body_item->userdata);
- if(message) {
- *fetch_body_item = *message_to_body_item(current_room, message.get(), current_room->get_user_display_name(me), me->user_id);
+ if(fetch_message_result.message) {
+ *fetch_body_item = *message_to_body_item(current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id);
event_data->status = FetchStatus::FINISHED_LOADING;
- event_data->message = message.get();
+ event_data->message = fetch_message_result.message.get();
fetch_body_item->userdata = event_data;
} else {
fetch_body_item->set_description("Failed to load message!");
event_data->status = FetchStatus::FAILED_TO_LOAD;
}
} else if(fetch_message_tab == MESSAGES_TAB_INDEX) {
- if(message) {
- fetch_body_item->embedded_item = message_to_body_item(current_room, message.get(), current_room->get_user_display_name(me), me->user_id);
+ if(fetch_message_result.message) {
+ fetch_body_item->embedded_item = message_to_body_item(current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id);
fetch_body_item->embedded_item_status = FetchStatus::FINISHED_LOADING;
} else {
fetch_body_item->embedded_item_status = FetchStatus::FAILED_TO_LOAD;
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index ce003a0..a48bcdd 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -99,16 +99,21 @@ namespace QuickMedia {
}
UserInfo::UserInfo(RoomData *room, std::string user_id) :
- room(room), display_name_color(user_id_to_color(user_id)), user_id(user_id)
+ room(room), display_name_color(user_id_to_color(user_id)), user_id(user_id), resolve_state(UserResolveState::NOT_RESOLVED)
{
display_name = std::move(user_id);
}
UserInfo::UserInfo(RoomData *room, std::string user_id, std::string display_name, std::string avatar_url) :
- room(room), display_name_color(user_id_to_color(user_id)), user_id(std::move(user_id)), display_name(std::move(display_name)), avatar_url(std::move(avatar_url)) {
+ room(room), display_name_color(user_id_to_color(user_id)), user_id(std::move(user_id)), resolve_state(UserResolveState::RESOLVED), display_name(std::move(display_name)), avatar_url(std::move(avatar_url)) {
}
+ // TODO: Remove this when images are embedded inside the text instead of using the same space as the author
+ bool is_visual_media_message_type(MessageType message_type) {
+ return message_type == MessageType::VIDEO || message_type == MessageType::IMAGE;
+ }
+
std::shared_ptr<UserInfo> RoomData::get_user_by_id(const std::string &user_id) {
std::lock_guard<std::recursive_mutex> lock(room_mutex);
auto user_it = user_info_by_user_id.find(user_id);
@@ -147,11 +152,13 @@ namespace QuickMedia {
user->display_name = std::move(display_name);
if(user->display_name.empty())
user->display_name = user->user_id;
+ user->resolve_state = UserResolveState::RESOLVED;
}
void RoomData::set_user_avatar_url(std::shared_ptr<UserInfo> &user, std::string avatar_url) {
std::lock_guard<std::recursive_mutex> lock(user_mutex);
user->avatar_url = std::move(avatar_url);
+ user->resolve_state = UserResolveState::RESOLVED;
}
void RoomData::prepend_messages_reverse(const std::vector<std::shared_ptr<Message>> &new_messages) {
@@ -1477,7 +1484,7 @@ namespace QuickMedia {
std::string avatar_url_str;
const rapidjson::Value &avatar_url_json = GetMember(json, "avatar_url");
if(avatar_url_json.IsString())
- avatar_url_str = avatar_url_json.GetString();
+ avatar_url_str = std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength());
const rapidjson::Value &display_name_json = GetMember(json, "displayname");
@@ -1485,9 +1492,12 @@ namespace QuickMedia {
std::string avatar_url = thumbnail_url_extract_media_id(avatar_url_str);
if(!avatar_url.empty())
avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + avatar_url + "?width=32&height=32&method=crop"; // TODO: Remove the constant strings around to reduce memory usage (6.3mb)
- auto user_info = std::make_shared<UserInfo>(room_data, user_id, std::move(display_name), std::move(avatar_url));
+ //auto user_info = std::make_shared<UserInfo>(room_data, user_id, std::move(display_name), std::move(avatar_url));
// Overwrites user data
- room_data->add_user(user_info);
+ //room_data->add_user(user_info);
+ auto user_info = get_user_by_id(room_data, user_id);
+ room_data->set_user_display_name(user_info, std::move(display_name));
+ room_data->set_user_avatar_url(user_info, std::move(avatar_url));
return user_info;
}
@@ -3289,9 +3299,14 @@ namespace QuickMedia {
auto user = room->get_user_by_id(user_id);
if(user)
return user;
- #if 0
- // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit
- // (and take into account that notification response may have notifications after call to sync above).
+
+ //fprintf(stderr, "Unknown user: %s, creating locally... synapse bug?\n", user_id.c_str());
+ auto user_info = std::make_shared<UserInfo>(room, user_id);
+ room->add_user(user_info);
+ return user_info;
+ }
+
+ void Matrix::update_user_with_latest_state(RoomData *room, const std::string &user_id) {
char url[512];
snprintf(url, sizeof(url), "%s/_matrix/client/r0/profile/%s", homeserver.c_str(), user_id.c_str());
@@ -3299,18 +3314,52 @@ namespace QuickMedia {
DownloadResult download_result = download_json(json_root, url, {}, true);
if(download_result != DownloadResult::OK || !json_root.IsObject()) {
fprintf(stderr, "Fetching profile for user %s failed!\n", user_id.c_str());
- return nullptr;
+ auto user = get_user_by_id(room, user_id);
+ assert(user);
+ user->resolve_state = UserResolveState::RESOLVED;
+ return;
}
- // Is this a synapse bug? sometimes lazy_fetch_members doesn't contain all related clients
- fprintf(stderr, "User was not available locally, fetched from server...\n");
- return parse_user_info(json_root, user_id, room);
- #else
- fprintf(stderr, "Unknown user: %s, creating locally... synapse bug?\n", user_id.c_str());
- auto user_info = std::make_shared<UserInfo>(room, user_id);
- room->add_user(user_info);
- return user_info;
- #endif
+ parse_user_info(json_root, user_id, room);
+ }
+
+ void Matrix::update_room_users(RoomData *room) {
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ char url[512];
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/joined_members", homeserver.c_str(), room->id.c_str());
+
+ rapidjson::Document json_root;
+ DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true);
+ if(download_result != DownloadResult::OK || !json_root.IsObject()) {
+ fprintf(stderr, "Fetching users for room %s failed!\n", room->id.c_str());
+ return;
+ }
+
+ const rapidjson::Value &joined_json = GetMember(json_root, "joined");
+ if(!joined_json.IsObject())
+ return;
+
+ for(auto const &joined_obj : joined_json.GetObject()) {
+ if(!joined_obj.name.IsString() || !joined_obj.value.IsObject())
+ continue;
+
+ const rapidjson::Value &avatar_url_json = GetMember(joined_obj.value, "avatar_url");
+ const rapidjson::Value &display_name_json = GetMember(joined_obj.value, "display_name");
+ auto user = get_user_by_id(room, std::string(joined_obj.name.GetString(), joined_obj.name.GetStringLength()));
+ assert(user);
+
+ std::string display_name = display_name_json.IsString() ? display_name_json.GetString() : user_id;
+ std::string avatar_url;
+ if(avatar_url_json.IsString())
+ avatar_url = std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength());
+ if(!avatar_url.empty())
+ avatar_url = homeserver + "/_matrix/media/r0/thumbnail/" + thumbnail_url_extract_media_id(avatar_url) + "?width=32&height=32&method=crop"; // TODO: Remove the constant strings around to reduce memory usage (6.3mb)
+ room->set_user_avatar_url(user, std::move(avatar_url));
+ room->set_user_display_name(user, std::move(display_name));
+ }
}
// TODO: GET the filter to check if its valid?