indexing description: "[ Extension to local highscore which can send local highscore to the server and receive a remote highscore. ]" date: "$Date$" revision: "$Revision$" class EM_HIGHSCORE inherit EM_LOCAL_HIGHSCORE redefine make_extended end EM_SHARED_SUBSYSTEMS export {NONE} all {ANY} network_subsystem end create make, make_extended feature -- Initialization make_extended (the_other_keys: ARRAY [STRING]) is -- create highscore with additional keys do Precursor {EM_LOCAL_HIGHSCORE} (the_other_keys) num_of_remote_entries := 10 create remote_highscore.make create connection_closed_event create connection_failed_event create connection_established_event create http.make end feature -- Element change set_num_of_remote_entries (a_num_of_remote_entries: like num_of_remote_entries) is -- Set `num_of_remote_entries' to `a_num_of_remote_entries'. do num_of_remote_entries := a_num_of_remote_entries ensure num_of_remote_entries_assigned: num_of_remote_entries = a_num_of_remote_entries end set_url (an_url: STRING) is -- set url to synchronize highscore require url_not_void: an_url /= void do url := an_url end feature -- Crop crop_remote (nb_elements: INTEGER) is -- crop remote highscore to 'nb_elements' require positive: nb_elements > 0 do remote_highscore.keep_first (nb_elements.min (remote_highscore.count)) end feature {NONE} -- Implementation add_entries_from_remote_string (the_entries: STRING) is -- Parse string received from server and add entries to remote highscore require string_not_void: the_entries /= void local sections: LIST[STRING] keys: ARRAY[STRING] values: ARRAY[STRING] split: INTEGER num_of_entries: INTEGER new_entry: like highscore_entries i, j, k, index: INTEGER current_value, current_key: STRING isolated_value: STRING do create remote_highscore.make num_of_entries := num_of_entries.max_value sections := the_entries.split ('&') create keys.make(1, sections.count) create values.make(1, sections.count) from sections.start until sections.after loop if sections.item.has ('=') then split := sections.item.index_of ('=', 1) keys.put (sections.item.substring (1, split - 1), sections.index) values.put (sections.item.substring(split + 1, sections.item.count), sections.index) num_of_entries := num_of_entries.min (values.item (sections.index).occurrences ('|') + 1) end sections.forth end if num_of_entries = num_of_entries.max_value then num_of_entries := 0 end from i := 1 until i > num_of_entries loop create new_entry.make(sections.count) from j := 1 until j > keys.count loop index := values.item(j).index_of ('|', 1) current_value := values.item(j) current_key := keys.item(j) if index /= 0 then isolated_value := current_value.substring (1, index - 1) values.put (current_value.substring (index + 1, current_value.count), j) else isolated_value := current_value end if current_key.is_equal(name) and then is_valid_string (isolated_value) then new_entry.set_name(isolated_value) elseif current_key.is_equal(score) and then isolated_value.is_integer then new_entry.set_score(isolated_value.to_integer) else new_entry.force(isolated_value, current_key) end j := j + 1 end if new_entry.name /= void and new_entry.score /= 0 then -- Fill in missing keys from k := 1 until k > other_keys.count loop if not new_entry.has (other_keys.item (k)) then new_entry.force ("", other_keys.item(k)) end k := k + 1 end remote_highscore.put_last(new_entry) end i := i + 1 end end feature -- Sorting sort_remote_by_name (sort_reverse: BOOLEAN) is -- sort remote highscore by name do sort (remote_highscore, name, sort_reverse) end sort_remote_by_score (sort_reverse: BOOLEAN) is -- sort remote highscore by score do sort (remote_highscore, score, sort_reverse) end sort_remote_by_key (a_key: STRING; sort_reverse: BOOLEAN) is -- sort remote highscore by key (sorting alphabetically) do sort (remote_highscore, a_key, sort_reverse) end feature -- Access remote_highscore: DS_LINKED_LIST[like highscore_entries] -- data structure with remote highscore in it num_of_remote_entries: INTEGER -- desired number of entries in remote highscore url: STRING -- url to synchronize highscore with feature -- Network synchronize_with_server is -- send local highscore to server, receive remote highscore require network_enabled: network_subsystem.is_enabled do send_and_receive (true, true) end send_local is -- send local highscore to server require network_enabled: network_subsystem.is_enabled do send_and_receive (true, false) end receive_remote is -- receive remote highscore require network_enabled: network_subsystem.is_enabled do send_and_receive (false, true) end cancel_connection is -- cancel the connection to server require network_enabled: network_subsystem.is_enabled do connection_cancelled := true if http.is_connected then http.disconnect wipe_out_event_subscriptions end end connection_closed_event: EM_EVENT_CHANNEL [TUPLE []] -- Event to indicate that the connection closed connection_failed_event: EM_EVENT_CHANNEL [TUPLE []] -- Event to indicate that the connection failed connection_established_event: EM_EVENT_CHANNEL [TUPLE []] -- Event to indicate that the connection is established connection_cancelled: BOOLEAN feature {NONE} -- Network Implementation send_and_receive (do_send, do_receive: BOOLEAN) is -- connect to server, send local and receive highscores if specified require network_enabled: network_subsystem.is_enabled do if not http.is_trying_to_connect then connection_cancelled := false if http.is_connected then http.disconnect end http.reset checksum_generator.reset -- Set up the parameters create parameters.make_empty parameters.append_string ("gameidlogin=" + application_id.item) checksum_generator.append_string (application_id.item) parameters.append_character (parameter_split) if do_receive then parameters.append_string ("limit=" + num_of_remote_entries.out) checksum_generator.append_string (num_of_remote_entries.out) else parameters.append_string ("limit=0") checksum_generator.append_string ("0") end parameters.append_character (parameter_split) parameters.append_string ("clientid=" + client_id.out) checksum_generator.append_string (client_id.out) parameters.append_character (parameter_split) if do_send then parameters.append_string(serialize_local_highscore) end checksum_generator.generate_checksum parameters.append_string ("checksum=" + checksum_generator.checksum.out) -- Handle Connection Stuff if do_receive and then not http.data_received_event.has (agent handle_data(?)) then http.data_received_event.subscribe (agent handle_data(?)) end if not http.connection_closed_event.has (agent handle_connection_closed) then http.connection_closed_event.subscribe (agent handle_connection_closed) end if not http.connection_failed_event.has (agent handle_connection_failed) then http.connection_failed_event.subscribe (agent handle_connection_failed) end if not http.connection_established_event.has (agent handle_connected) then http.connection_established_event.subscribe (agent handle_connected) end http.set_hostname (url.substring (1, url.index_of ('/', 1) - 1)) http.set_user_agent (application_id.item) create received_data.make_empty http.connect end end handle_data (the_data: STRING) is -- handle data from server do if not connection_cancelled then received_data.append_string(the_data) end end handle_connected is -- handle connection established event do if not connection_cancelled then -- HTTP GET -- http.set_path (url.substring (url.index_of ('/', 1), url.count) + "?" + parameters) -- http.get -- HTTP POST http.set_path (url.substring (url.index_of ('/', 1), url.count)) http.post (parameters) connection_established_event.publish ([]) end end handle_connection_closed is -- handle connection close event do if not connection_cancelled then add_entries_from_remote_string (received_data) sort_remote_by_score (false) crop_remote (num_of_remote_entries) connection_closed_event.publish ([]) wipe_out_event_subscriptions end end handle_connection_failed is -- handle connection failure event do if not connection_cancelled then connection_failed_event.publish ([]) wipe_out_event_subscriptions connection_cancelled := true end end wipe_out_event_subscriptions is -- wipe out all subscriptions that highscore's client has do connection_closed_event.subscriptions.wipe_out connection_failed_event.subscriptions.wipe_out connection_established_event.subscriptions.wipe_out end serialize_local_highscore: STRING is -- serialize local highscore in order to send do server -- generates a checksum while doing so local i: INTEGER do create Result.make_empty -- Name Result.append_string (name) Result.append_character (parameter_assignment) from local_highscore.start until local_highscore.after loop checksum_generator.append_string (local_highscore.item_for_iteration.name) Result.append_string (local_highscore.item_for_iteration.name) Result.append_character (parameter_string_split) local_highscore.forth end -- remove last 'Result_string_split' Result.keep_head (Result.count - 1) Result.append_character (parameter_split) --Score Result.append_string (score) Result.append_character (parameter_assignment) from local_highscore.start until local_highscore.after loop checksum_generator.append_string (local_highscore.item_for_iteration.score.out) Result.append_string (local_highscore.item_for_iteration.score.out) Result.append_character (parameter_string_split) local_highscore.forth end -- remove last 'Result_string_split' Result.keep_head (Result.count - 1) Result.append_character (parameter_split) from i := 1 until i > other_keys.count loop Result.append_string (other_keys.item (i)) Result.append_character (parameter_assignment) from local_highscore.start until local_highscore.after loop checksum_generator.append_string (local_highscore.item_for_iteration.item (other_keys.item (i))) Result.append_string (local_highscore.item_for_iteration.item (other_keys.item (i))) Result.append_character (parameter_string_split) local_highscore.forth end -- remove last 'Result_string_split' Result.keep_head (Result.count - 1) Result.append_character (parameter_split) i := i + 1 end end feature {NONE} -- Implementation Attributes http: EM_HTTP_PROTOCOL received_data: STRING parameter_start: CHARACTER is '?' parameter_assignment: CHARACTER is '=' parameter_split: CHARACTER is '&' parameter_string_split: CHARACTER is '|' parameters: STRING end