I'm imagining something like this:
# client
Meteor.call 'attachData', localStorage.getItem 'clientData'
Meteor.connection.onReconnect ->
Meteor.call 'attachData', localStorage.getItem 'clientData'
setTimeout ->
Meteor.call 'logData'
, 1000
# server
Meteor.methods
attachData: (data) ->
this.connection.data = data
logData: ->
console.log this.connection.data
This seems to be working, but I'm unfamiliar with this.connection. Given this setup, will this.connection.data always be there? Should I be using Meteor.connection or Meteor.default_connection?
It appears to work. The data is deleted when connection is broken, either by page reload or break in internet connection.
https://github.com/lorensr/connection-data
Related
I have a streams publication and subscription setup but for some reason if I do the following in my route the view gets no data:
Router.route '/seasons/:season/episodes/:episode',
name: 'episode'
action: ->
#render(
'episode',
data: ->
Streams.findOne({season: #params.season, episode: #params.episode})
)
If I log the params they are there as expected, and doing a findOne manually either via the db or the browser console returns the data as expected.
If I remove the params so it just does Streams.findOne() the data returns the first stream from the database and the view has access to it as expected. I'm really not sure what's going on here.
You probably need to wait on your streams publication before trying to access the data : Pub/Sub mechanism in Meteor is asynchronous, when you subscribe to some data, you don't instantly get it back in the browser because of the underlying client/server latency.
Try reorganizing your code as follow :
Router.route '/seasons/:season/episodes/:episode',
name: 'episode'
template: 'episode'
data: ->
Streams.findOne({season: #params.season, episode: #params.episode})
waitOn: ->
Meteor.subscribe 'streams', #params.season, #params.episode
I have a problem for which I do not know the solution. I am creating game that involves multiple players. One person "hosts" the game and sends "invitations" to other users to join the "lobby". If you've ever played Call of Duty or any similar game, it's the same concept.
Much of this process currently works properly.
I created a collection called Lobby to keep track of all the open and closed lobbies. When a user wants to host a lobby, he clicks on the button, which creates a new lobby and redirects the user to the proper url:
Template.lobby.events
'click button#host-lobby': (e,t) ->
lobbyId = Lobby.insert
host: Meteor.user()._id
hostName: Meteor.user().username
status: true
players: []
Router.go("currentLobby", _id: lobbyId)
Then, the user can invite other users to the lobby (url) through a modal, which adds a notification object to the invited user's profile. Probably not the best way to do it, so I'm open to suggestions on this front.
Template._playerItem.events
'click button': (e,t) ->
lobbyId = Session.get "currentLobby"
Meteor.call "sendNotification", #_id, lobbyId, Meteor.user().username
Lobby.update
_id: lobbyId
,
$addToSet:
invitedPlayers: #_id
And the method:
Meteor.methods
sendNotification: (userId, lobbyId, hostName) ->
sendTo = Meteor.users.findOne(_id: userId)
Meteor.users.update
_id: userId
,
$push:
invite:
hostName: hostName
lobbyId: lobbyId
So at this point, the user can either accept or decline the invitation. If he accepts, he is routed to the lobby and is added to the players array in the lobby object. The user shows up in the list of players as one would expect.
My issue starts when I attempt to "start" the game. When the button is clicked, the game is created properly and the host (who pressed the button) is routed to the url for the new game:
Template.currentLobby.events
'click #start-game': (e,t) ->
playerIds = [#host]
#players.forEach (player) ->
playerIds.push(player.id)
Meteor.call 'createGame', playerIds
Router.go('home')
The problem is that the other users in the lobby are not redirected. They have access to the game if they manually go to the url, but they aren't taken there. They have no way of knowing that the game has actually started...
One solution is to add a "Game has started" badge, with a link to the game, but I think a more elegant solution is to route all of the users at the current lobby url to the game that was just started.
Is this functionality possible? Is there a better way to host a lobby?
EDIT
Thank you Chet for the solution. This is how I eventually implemented it:
Template.currentLobby.rendered = ->
#autorun ->
data = Template.currentData()
if data.url
Router.go data.url
#autorun had some context difficulties, so I just used the lobby data. Then, when someone clicks on the "start game" button, the current lobby is updated with the url of the new game (The Meteor.call 'createGame' returns the _id of the new game).
Template.currentLobby.events
'click #start-game': (e,t) ->
playerIds = [#host]
lobbyId = Template.currentData()._id
#players.forEach (player) ->
playerIds.push(player.id)
Meteor.call 'createGame', playerIds, (err, res) ->
Lobby.update
_id: lobbyId
,
$set:
url: "/game/#{res}"
Works like a charm. Thanks!
OK. First, to address your notifications. This is a valid solution. However, you'll probably want to sort them by date! You'll also want to create a separate Notifications collection. Anytime you update a document, the whole thing is send over DDP to the client. Thus any new notification added to the profile will result the entire profile being sent to the client. You'll also want some way of marking a notification as being read so it can be deleted.
To address you question about redirecting the lobby, create property called url. Initially it is set to false. Once the host is ready to start the game, they set the url property to the url.
When a user enters a lobby, start an autorun for the redirect.
Template.lobby.rendered = ->
#autorun ->
if game.url
Router.go(game.url)
You'll have to make sure that the game.url is a reactive datasource. If you pass the game as the data context using iron router, then you should be able to use #data.url but I'm not 100% sure it will be reactive. Just to be safe, try Games.findOne(#data._id) -- that will certainly be reactive.
EDIT:
Just to be clear, try this:
Template.lobby.rendered = ->
#autorun ->
game = Games.findOne(#data._id)
if game.url
Router.go(game.url)
I have a Meteor template that renders the documents of a collection. The subscription is set up in the template's autorun:
Template.userSearch.rendered = ->
Session.set "userSearchLimit", 5
#autorun ->
Meteor.subscribe "userSearch", "something to find", Session.get "userSearchLimit"
When the user pressed a "Load more" button I increment the userSearchLimit session variable, which causes the autorun to rerun. The subscription changes since I ask for more data, so the old subscription will be torn down and a new one created, however the first part of the actual data will be the same.
The problem is that the entire list is being redrawn, causing a horrible flicker. When I put debug logging into the find() method as suggested here, then I see that the documents are first being removed, then added back again.
I would have expected the server to avoid resending data that already exists on the client.
How can I solve this?
To further confuse the issue I also tried using ddp-analyzer to see what data was being sent. As soon as I use that then only the new data gets sent and the flicker is eliminated. As soon as I stop using it the problem comes back.
I've solved this by manually waiting for the new subscription to be ready before taking down the previous one:
currentMatchingSub = null
prevMatchingSub = null
subscribeToUserSearch = (limit) ->
prevMatchingSub = currentMatchingSub
currentMatchingSub = Meteor.subscribe "userSearch", "john baker", limit, ->
# Only once the new subscription is ready should be take down the previous one. This ensure's
# that there's no flicker...
prevMatchingSub.stop() if prevMatchingSub?
prevMatchingSub = null
Template.userSearch.rendered = ->
limit = 5
Session.set "userSearchLimit", limit
subscribeToUserSearch limit
Template.userSearch.destroyed = ->
prevMatchingSub.stop() if prevMatchingSub?
currentMatchingSub.stop() if currentMatchingSub?
I've got a simple CSV file with 40,000 rows which I'm processing browser-side with papa-parse.
I'm trying to insert them one-by-one into a collection using the techniques in Discover Meteor and other 101 posts I find when Googling.
40000 insert browser-side pretty quickly - but when I check mongo server side it's only got 387 records.
Eventually (usually after 20 seconds or so) it starts to insert server-side.
But if I close or interrupt the browser, the already-inserted records disappear obviously.
How do I force inserts to go server-side, or at least monitor so I know when to notify the user of success?
I tried Tracker.flush() no difference.
I'd go server-side inserts in a Meteor.method, but all the server-side CSV libraries are more complex to operate than client-side (I'm a beginner to pretty much everything programming :)
Thanks!
This is the main part of my code (inside client folder):
Template.hello.events({
"submit form": function (event) {
event.preventDefault();
var reader = new FileReader();
reader.onload = function (event) {
var csv = Papa.parse(this.result, {header: true});
var count = 0;
_.forEach(csv.data, function (csvPerson) {
count++;
Person.insert(csvPerson);
console.log('Inserting: ' + count + ' -> ' + csvPerson.FirstName);
});
};
reader.readAsText(event.target[0].files[0]);
}
});
The last few lines of console output:
Inserting: 39997 -> Joan
Inserting: 39998 -> Sydnee
Inserting: 39999 -> Yael
Inserting: 40000 -> Kirk
The last few lines of CSV (random generated data):
Jescie,Ayala,27/10/82,"P.O. Box 289, 5336 Tristique Road",Dandenong,7903,VI,mus.Proin#gravida.co.uk
Joan,Petersen,01/09/61,299-1763 Aliquam Rd.,Sydney,1637,NS,sollicitudin#Donectempor.ca
Sydnee,Oliver,30/07/13,Ap #648-5619 Aliquam Av.,Albury,1084,NS,Nam#rutrumlorem.ca
Yael,Barton,30/12/66,521 Auctor. Rd.,South Perth,2343,WA,non.cursus.non#etcommodo.co.uk
Kirk,Camacho,25/09/08,"Ap #454-7701 A, Road",Stirling,3121,WA,dictum.eu#morbitristiquesenectus.com
The hello template is a simple form obviously, just file select and submit.
Client code is under client directory.
Person defined in a file in application root.
CSV parsed as strings for now, to avoid complexity.
The records inserted look fine, retrieve by name, whatever.
Person.find().count() browser-side in console results in 40000.
Happy to send the file, which is only 1.5MB and it's random data - not sensitive.
I think call() should work as follows:
On client side
Meteor.call("insertMethod",csvPerson);
And method on server side
insertMethod: function(csvPerson){
Person.insert(csvPerson);
}
In Meteor, on some scenarios, if you don't pass a callback the operation will sync.
If you run the code Person.insert(csvPerson); on the server, the operation will be sync not async. Depending on what you want to do, you might have serious problems in the future. On the client, it won't be sync but async.
Since node.js is an event-based server, a single sync operation can halt the entire system. You've to be really about your sync operations.
For importing data, the best option is to do at server-side inside Meteor.startup(function(){ //import code goes here}).
The solution propose by Sindis works but it slow and if the browser closes (for some reason), you're not keeping a track of the already inserted records. If you use Meteor.call("insertMethod",csvPerson);, this operation will be sync on the client.
The best option on your beginner scenario (not optimal) is to:
1- While (You have record to insert)
2- Call Meteor.call without a callback
3- Count all the inserted fields in the Collection
4- Save this value to localStorage
5- Go back to step 1
This works assuming that the order of insertion is the same on every insert attempt. If you browser fails, you can always get the value from localStorage and skip that number of records.
I'm coding on a simple web crawler and have generated a bunch gf static files I try to crawl by the code at bottom. I have two issues/questions I don't have an idea for:
1.) Looping over the sequence 1..200 throws me an error exactly after 100 pages have been crawled:
** exception error: no match of right hand side value {error,socket_closed_remotely}
in function erlang_test_01:fetch_page/1 (erlang_test_01.erl, line 11)
in call from lists:foreach/2 (lists.erl, line 1262)
2.) How to parallelize the requests, e.g. 20 cincurrent reqs
-module(erlang_test_01).
-export([start/0]).
-define(BASE_URL, "http://46.4.117.69/").
to_url(Id) ->
?BASE_URL ++ io_lib:format("~p", [Id]).
fetch_page(Id) ->
Uri = to_url(Id),
{ok, {{_, Status, _}, _, Data}} = httpc:request(get, {Uri, []}, [], [{body_format,binary}]),
Status,
Data.
start() ->
inets:start(),
lists:foreach(fun(I) -> fetch_page(I) end, lists:seq(1, 200)).
1. Error message
socket_closed_remotely indicates that the server closed the connection, maybe because you made too many requests in a short timespan.
2. Parallellization
Create 20 worker processes and one process holding the URL queue. Let each process ask the queue for a URL (by sending it a message). This way you can control the number of workers.
An even more "Erlangy" way is to spawn one process for each URL! The upside to this is that your code will be very straightforward. The downside is that you cannot control your bandwidth usage or number of connections to the same remote server in a simple way.