diff --git a/src/Unit-3/lesson5/Completed/ActorPaths.cs b/src/Unit-3/lesson5/Completed/ActorPaths.cs deleted file mode 100644 index ae491dd9b..000000000 --- a/src/Unit-3/lesson5/Completed/ActorPaths.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Akka.Actor; - -namespace GithubActors -{ - /// - /// Static helper class used to define paths to fixed-name actors - /// (helps eliminate errors when using ) - /// - public static class ActorPaths - { - public static readonly ActorMetaData GithubAuthenticatorActor = new ActorMetaData("authenticator", "akka://GithubActors/user/authenticator"); - public static readonly ActorMetaData MainFormActor = new ActorMetaData("mainform", "akka://GithubActors/user/mainform"); - public static readonly ActorMetaData GithubValidatorActor = new ActorMetaData("validator", "akka://GithubActors/user/validator"); - public static readonly ActorMetaData GithubCommanderActor = new ActorMetaData("commander", "akka://GithubActors/user/commander"); - public static readonly ActorMetaData GithubCoordinatorActor = new ActorMetaData("coordinator", "akka://GithubActors/user/commander/coordinator"); - } - - /// - /// Meta-data class - /// - public class ActorMetaData - { - public ActorMetaData(string name, string path) - { - Name = name; - Path = path; - } - - public string Name { get; private set; } - - public string Path { get; private set; } - } -} diff --git a/src/Unit-3/lesson5/Completed/ActorSystem.fs b/src/Unit-3/lesson5/Completed/ActorSystem.fs new file mode 100644 index 000000000..f1242afe1 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/ActorSystem.fs @@ -0,0 +1,6 @@ +namespace GithubActors + +open Akka.FSharp + +module ActorSystem = + let githubActors = System.create "GithubActors" (Configuration.load()) \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/Actors.fs b/src/Unit-3/lesson5/Completed/Actors.fs new file mode 100644 index 000000000..458a5c306 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/Actors.fs @@ -0,0 +1,462 @@ +namespace GithubActors + +open System +open System.Windows.Forms +open System.Drawing +open System.Linq +open Akka.FSharp +open Akka.Actor +open Akka.Routing + +[] +module Actors = + + // make a pipe-friendly version of Akka.NET PipeTo for handling async computations + let pipeToWithSender recipient sender asyncComp = pipeTo asyncComp recipient sender + + // Helper functions to check the type of query received + let isWorkerMessage (someType: obj) = someType.GetType().IsSubclassOf(typeof) + + let isQueryStarrers (someType: obj) = + if isWorkerMessage someType then + match someType :?> GithubActorMessage with + | QueryStarrers _ -> true + | _ -> false + else + false + + let isQueryStarrer (someType: obj) = + if isWorkerMessage someType then + match someType :?> GithubActorMessage with + | QueryStarrer _ -> true + | _ -> false + else + false + + // Actors + let githubAuthenticationActor (statusLabel: Label) (githubAuthForm: Form) (launcherForm: Form) (mailbox: Actor<_>) = + + let cannotAuthenticate reason = + statusLabel.ForeColor <- Color.Red + statusLabel.Text <- reason + + let showAuthenticatingStatus () = + statusLabel.Visible <- true + statusLabel.ForeColor <- Color.Orange + statusLabel.Text <- "Authenticating..." + + let rec unauthenticated () = + actor { + let! message = mailbox.Receive () + + match message with + | Authenticate token -> + showAuthenticatingStatus () + let client = GithubClientFactory.getUnauthenticatedClient () + client.Credentials <- Octokit.Credentials token + + let continuation (task: System.Threading.Tasks.Task) : AuthenticationMessage = + match task.IsFaulted with + | true -> AuthenticationFailed + | false -> + match task.IsCanceled with + | true -> AuthenticationCancelled + | false -> + GithubClientFactory.setOauthToken token + AuthenticationSuccess + + client.User.Current().ContinueWith continuation + |> Async.AwaitTask + |!> mailbox.Self + + return! authenticating () + | _ -> return! unauthenticated () + } + and authenticating () = + actor { + let! message = mailbox.Receive () + + match message with + | AuthenticationFailed -> + cannotAuthenticate "Authentication failed." + return! unauthenticated () + | AuthenticationCancelled -> + cannotAuthenticate "Authentication timed out." + return! unauthenticated () + | AuthenticationSuccess -> + githubAuthForm.Hide () + launcherForm.Show () + | _ -> return! authenticating () + } + + unauthenticated () + + + let mainFormActor (isValidLabel: Label) (createRepoResultsForm) (mailbox: Actor<_>) = + + let updateLabel message isValid = + isValidLabel.Text <- message + if isValid then isValidLabel.ForeColor <- Color.Green else isValidLabel.ForeColor <- Color.Red + mailbox.UnstashAll () + + let rec ready () = + actor { + let! message = mailbox.Receive () + + match message with + | ProcessRepo uri -> + select "akka://GithubActors/user/validator" mailbox.Context.System + let repoResultsForm: Form = createRepoResultsForm repoKey coordinator + repoResultsForm.Show () + return! ready () + | _ -> return! ready () + } + and busy () = + actor { + let! message = mailbox.Receive () + + match message with + | ValidRepo _ -> + updateLabel "Valid!" true + return! ready () + | InvalidRepo (uri, reason) -> + updateLabel reason false + return! ready () + | UnableToAcceptJob job -> + updateLabel (sprintf "%s/%s is a valid repo, but the system cannot accept additional jobs" job.Owner job.Repo) false + return! ready () + | AbleToAcceptJob job -> + updateLabel (sprintf "%s/%s is a valid repo - starting job!" job.Owner job.Repo) true + return! ready () + | LaunchRepoResultsWindow (_, _) -> + mailbox.Stash () + return! busy () + | _ -> return! busy () + } + + ready () + + + let githubValidatorActor (getGithubClient: unit -> Octokit.GitHubClient) (mailbox: Actor<_>) = + + let splitIntoOwnerAndRepo repoUri = + let results = Uri(repoUri, UriKind.Absolute).PathAndQuery.TrimEnd('/').Split('/') |> Array.rev + (results.[1], results.[0]) // User, Repo + + let rec processMessage () = actor { + let! message = mailbox.Receive () + + match message with + // outright invalid URLs + | ValidateRepo uri when uri |> String.IsNullOrEmpty || not (Uri.IsWellFormedUriString(uri, UriKind.Absolute)) -> + mailbox.Context.Sender + let continuation (task: System.Threading.Tasks.Task) : GithubActorMessage = + match task.IsCanceled with + | true -> InvalidRepo(uri, "Repo lookup timed out") + | false -> + match task.IsFaulted with + | true -> InvalidRepo(uri, "Not a valid absolute URI") + | false -> ValidRepo task.Result + + let (user, repo) = splitIntoOwnerAndRepo uri + let githubClient = getGithubClient () + + githubClient.Repository.Get(user, repo).ContinueWith continuation + |> Async.AwaitTask + |> pipeToWithSender mailbox.Self mailbox.Context.Sender // send the message back to ourselves but pass the real sender through + | InvalidRepo (uri, reason) -> + InvalidRepo(uri, reason) |> mailbox.Context.Sender.Forward + | ValidRepo repo -> + mailbox.Context.ActorSelection("akka://GithubActors/user/commander") + mailbox.Context.ActorSelection("akka://GithubActors/user/mainform") + mailbox.Context.ActorSelection("akka://GithubActors/user/mainform") return! processMessage () + + return! processMessage () + } + + processMessage () + + let githubWorkerActor (mailbox: Actor<_>) = + + let githubClient = lazy (GithubClientFactory.getClient ()) + + let rec processMessage () = actor { + let! message = mailbox.Receive () + + match message with + | RetryableQuery query when isQueryStarrer query.Query || isQueryStarrers query.Query -> + match query.Query :?> GithubActorMessage with + | QueryStarrer login -> + let sender = mailbox.Context.Sender + + let continuation (task: System.Threading.Tasks.Task>) : GithubActorMessage = + if task.IsFaulted || task.IsCanceled then + RetryableQuery(nextTry query) + else + StarredReposForUser(login, task.Result) + + githubClient.Value.Activity.Starring.GetAllForUser(login).ContinueWith continuation + |> Async.AwaitTask + |!> sender + | QueryStarrers repoKey -> + let sender = mailbox.Context.Sender + + let continuation (task: System.Threading.Tasks.Task>) : GithubActorMessage = + if task.IsFaulted || task.IsCanceled then + RetryableQuery(nextTry query) + else + task.Result |> Seq.toArray |> UsersToQuery // returns the list of users + + githubClient.Value.Activity.Starring.GetAllStargazers(repoKey.Owner, repoKey.Repo).ContinueWith continuation + |> Async.AwaitTask + |!> sender + | _ -> () // never reached + | _ -> () + + return! processMessage () + } + + processMessage () + + + let githubCoordinatorActor (mailbox: Actor<_>) = + + let startWorking repoKey (scheduler: IScheduler) = + { + ReceivedInitialUsers = false + CurrentRepo = repoKey + Subscribers = System.Collections.Generic.HashSet () + SimilarRepos = System.Collections.Generic.Dictionary () + GithubProgressStats = getDefaultStats () + PublishTimer = new Cancelable (scheduler) + } + + // pre-start + let githubWorker = spawnOpt mailbox.Context "worker" githubWorkerActor [ SpawnOption.Router(RoundRobinPool(10)) ] + + let rec waiting () = + actor { + let! message = mailbox.Receive () + + match message with + | CanAcceptJob repoKey -> + mailbox.Context.Sender + githubWorker return! waiting () + + return! waiting () + } + and working (settings: WorkerSettings) = + actor { + let! message = mailbox.Receive () + + match message with + // received a downloaded user back from the github worker + | StarredReposForUser (login, repos) -> + repos + |> Seq.iter (fun repo -> + if not <| settings.SimilarRepos.ContainsKey repo.HtmlUrl then + settings.SimilarRepos.[repo.HtmlUrl] <- { SimilarRepo.Repo = repo; SharedStarrers = 1 } + else + settings.SimilarRepos.[repo.HtmlUrl] <- increaseSharedStarrers settings.SimilarRepos.[repo.HtmlUrl] + ) + + return! working {settings with GithubProgressStats = userQueriesFinished settings.GithubProgressStats 1 } + | PublishUpdate -> + // Check to see if the job has fully completed + match settings.ReceivedInitialUsers && settings.GithubProgressStats.IsFinished with + | true -> + let finishStats = finish settings.GithubProgressStats + + // All repos minus forks of the current one + let sortedSimilarRepos = + settings.SimilarRepos.Values + |> Seq.filter (fun repo -> repo.Repo.Name <> settings.CurrentRepo.Repo) + |> Seq.sortBy (fun repo -> -repo.SharedStarrers) + + // Update progress (both repos and users) + settings.Subscribers + |> Seq.iter (fun subscriber -> + subscriber + settings.Subscribers + |> Seq.iter (fun subscriber -> subscriber + // queue all the jobs + users |> Seq.iter (fun user -> githubWorker + mailbox.Context.Sender + // this is our first subscriber, which means we need to turn publishing on + if settings.Subscribers.Count = 0 then + mailbox.Context.System.Scheduler.ScheduleTellRepeatedly( + TimeSpan.FromMilliseconds 100., TimeSpan.FromMilliseconds 30., + mailbox.Self, PublishUpdate, mailbox.Self, settings.PublishTimer) + settings.Subscribers.Add subscriber |> ignore + + // query failed, but can be retried + | RetryableQuery query when query.CanRetry -> + githubWorker + settings.Subscribers + |> Seq.iter (fun subscriber -> subscriber + return! working {settings with GithubProgressStats = incrementFailures settings.GithubProgressStats 1 } + | _ -> return! working settings + + return! working settings + } + + waiting () + + + let githubCommanderActor (mailbox: Actor<_>) = + + let timeout = Nullable(TimeSpan.FromSeconds 3.) + mailbox.Context.SetReceiveTimeout timeout + mailbox.Context.SetReceiveTimeout (Nullable()) + + // pre-start + let coordinator = spawnOpt mailbox.Context "coordinator" githubCoordinatorActor [ SpawnOption.Router(FromConfig.Instance) ] + + // post-stop, kill off the old coordinator so we can recreate it from scratch + mailbox.Defer (fun _ -> coordinator + match githubMessage with + | CanAcceptJob repoKey -> + coordinator Async.RunSynchronously + + mailbox.Context.SetReceiveTimeout (Nullable(TimeSpan.FromSeconds 3.)) + return! asking mailbox.Context.Sender (routees.Members.Count ()) + | _ -> return! ready canAcceptJobSender pendingJobReplies + | _ -> return! ready canAcceptJobSender pendingJobReplies + } + // pass around the actor that sent the CanAcceptJob message as well as the current number of pending jobs + and asking canAcceptJobSender pendingJobReplies = + actor { + let! message = mailbox.Receive () + + match box message with + | :? ReceiveTimeout as timeout -> + canAcceptJobSender + match githubMessage with + | CanAcceptJob repoKey -> + mailbox.Stash () + return! asking canAcceptJobSender pendingJobReplies + | UnableToAcceptJob repoKey -> + let currentPendingJobReplies = pendingJobReplies - 1 + if currentPendingJobReplies = 0 then + canAcceptJobSender + canAcceptJobSender return! asking canAcceptJobSender pendingJobReplies + | _ -> return! asking canAcceptJobSender pendingJobReplies + } + + ready null 0 + + + let repoResultsActor (usersGrid: DataGridView) (statusLabel: ToolStripStatusLabel) (progressBar: ToolStripProgressBar) (mailbox: Actor<_>) = + let startProgress stats = + progressBar.Minimum <- 0 + progressBar.Step <- 1 + progressBar.Maximum <- stats.ExpectedUsers + progressBar.Value <- stats.UsersThusFar + progressBar.Visible <- true + statusLabel.Visible <- true + + let displayProgress stats = + statusLabel.Text <- sprintf "%i out of %i users (%i failures) [%A elapsed]" stats.UsersThusFar stats.ExpectedUsers stats.QueryFailures stats.Elapsed + + let stopProgress repo = + progressBar.Visible <- true + progressBar.ForeColor <- Color.Red + progressBar.Maximum <- 1 + progressBar.Value <- 1 + statusLabel.Visible <- true + statusLabel.Text <- sprintf "Failed to gather date for GitHub repository %s / %s" repo.Owner repo.Repo + + let displayRepo similarRepo = + let repo = similarRepo.Repo + let row = new DataGridViewRow() + row.CreateCells usersGrid + row.Cells.[0].Value <- repo.Owner.Login + row.Cells.[1].Value <- repo.Owner.Name + row.Cells.[2].Value <- repo.Owner.HtmlUrl + row.Cells.[3].Value <- similarRepo.SharedStarrers + row.Cells.[4].Value <- repo.OpenIssuesCount + row.Cells.[5].Value <- repo.StargazersCount + row.Cells.[6].Value <- repo.ForksCount + usersGrid.Rows.Add row |> ignore + + let mutable hasSetProgress = false + let rec processMessage () = actor { + let! message = mailbox.Receive () + + match message with + | GithubProgressStats stats -> // progress update + if not hasSetProgress && stats.ExpectedUsers > 0 then + startProgress stats + hasSetProgress <- true + displayProgress stats + progressBar.Value <- stats.UsersThusFar + stats.QueryFailures + | SimilarRepos repos -> // user update + repos |> Seq.iter displayRepo + | JobFailed repoKey -> // critical failure, like not being able to connect to Github + stopProgress repoKey + | _ -> () + + return! processMessage () + } + + processMessage () \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/Actors/GithubAuthenticationActor.cs b/src/Unit-3/lesson5/Completed/Actors/GithubAuthenticationActor.cs deleted file mode 100644 index 8c2523ce9..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/GithubAuthenticationActor.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Drawing; -using Akka.Actor; -using Octokit; -using Label = System.Windows.Forms.Label; - -namespace GithubActors.Actors -{ - public class GithubAuthenticationActor : ReceiveActor - { - #region Messages - - public class Authenticate - { - public Authenticate(string oAuthToken) - { - OAuthToken = oAuthToken; - } - - public string OAuthToken { get; private set; } - } - - public class AuthenticationFailed { } - - public class AuthenticationCancelled { } - - public class AuthenticationSuccess { } - - #endregion - - private readonly Label _statusLabel; - private readonly GithubAuth _form; - - public GithubAuthenticationActor(Label statusLabel, GithubAuth form) - { - _statusLabel = statusLabel; - _form = form; - Unauthenticated(); - } - - private void Unauthenticated() - { - Receive(auth => - { - //need a client to test our credentials with - var client = GithubClientFactory.GetUnauthenticatedClient(); - GithubClientFactory.OAuthToken = auth.OAuthToken; - client.Credentials = new Credentials(auth.OAuthToken); - BecomeAuthenticating(); - client.User.Current().ContinueWith(tr => - { - if (tr.IsFaulted) - return new AuthenticationFailed(); - if (tr.IsCanceled) - return new AuthenticationCancelled(); - return new AuthenticationSuccess(); - }).PipeTo(Self); - }); - } - - private void BecomeAuthenticating() - { - _statusLabel.Visible = true; - _statusLabel.ForeColor = Color.Yellow; - _statusLabel.Text = "Authenticating..."; - Become(Authenticating); - } - - private void BecomeUnauthenticated(string reason) - { - _statusLabel.ForeColor = Color.Red; - _statusLabel.Text = "Authentication failed. Please try again."; - Become(Unauthenticated); - } - - private void Authenticating() - { - Receive(failed => BecomeUnauthenticated("Authentication failed.")); - Receive(cancelled => BecomeUnauthenticated("Authentication timed out.")); - Receive(success => - { - var launcherForm = new LauncherForm(); - launcherForm.Show(); - _form.Hide(); - }); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Actors/GithubCommanderActor.cs b/src/Unit-3/lesson5/Completed/Actors/GithubCommanderActor.cs deleted file mode 100644 index 80d37e6ca..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/GithubCommanderActor.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Linq; -using Akka.Actor; -using Akka.Routing; - -namespace GithubActors.Actors -{ - /// - /// Top-level actor responsible for coordinating and launching repo-processing jobs - /// - public class GithubCommanderActor : ReceiveActor, WithUnboundedStash - { - #region Message classes - - public class CanAcceptJob - { - public CanAcceptJob(RepoKey repo) - { - Repo = repo; - } - - public RepoKey Repo { get; private set; } - } - - public class AbleToAcceptJob - { - public AbleToAcceptJob(RepoKey repo) - { - Repo = repo; - } - - public RepoKey Repo { get; private set; } - } - - public class UnableToAcceptJob - { - public UnableToAcceptJob(RepoKey repo) - { - Repo = repo; - } - - public RepoKey Repo { get; private set; } - } - - #endregion - - private ActorRef _coordinator; - private ActorRef _canAcceptJobSender; - private int pendingJobReplies; - private RepoKey _repoJob; - - public GithubCommanderActor() - { - Ready(); - } - - private void Ready() - { - Receive(job => - { - _coordinator.Tell(job); - _repoJob = job.Repo; - BecomeAsking(); - }); - } - - private void BecomeAsking() - { - _canAcceptJobSender = Sender; - //block, but ask the router for the number of routees. Avoids magic numbers. - pendingJobReplies = _coordinator.Ask(new GetRoutees()).Result.Members.Count(); - Become(Asking); - - //send ourselves a ReceiveTimeout message if no message within 3 seonds - Context.SetReceiveTimeout(TimeSpan.FromSeconds(3)); - } - - private void Asking() - { - //stash any subsequent requests - Receive(job => Stash.Stash()); - - //means at least one actor failed to respond - Receive(timeout => - { - _canAcceptJobSender.Tell(new UnableToAcceptJob(_repoJob)); - BecomeReady(); - }); - - Receive(job => - { - pendingJobReplies--; - if (pendingJobReplies == 0) - { - _canAcceptJobSender.Tell(job); - BecomeReady(); - } - }); - - Receive(job => - { - _canAcceptJobSender.Tell(job); - - //start processing messages - Sender.Tell(new GithubCoordinatorActor.BeginJob(job.Repo)); - - //launch the new window to view results of the processing - Context.ActorSelection(ActorPaths.MainFormActor.Path).Tell(new MainFormActor.LaunchRepoResultsWindow(job.Repo, Sender)); - - BecomeReady(); - }); - } - - private void BecomeReady() - { - Become(Ready); - Stash.UnstashAll(); - - //cancel ReceiveTimeout - Context.SetReceiveTimeout(null); - } - - protected override void PreStart() - { - //create a broadcast router who will ask all if them if they're available for work - _coordinator = - Context.ActorOf(Props.Create(() => new GithubCoordinatorActor()).WithRouter(FromConfig.Instance), - ActorPaths.GithubCoordinatorActor.Name); - base.PreStart(); - } - - protected override void PreRestart(Exception reason, object message) - { - //kill off the old coordinator so we can recreate it from scratch - _coordinator.Tell(PoisonPill.Instance); - base.PreRestart(reason, message); - } - - public IStash Stash { get; set; } - } -} diff --git a/src/Unit-3/lesson5/Completed/Actors/GithubCoordinatorActor.cs b/src/Unit-3/lesson5/Completed/Actors/GithubCoordinatorActor.cs deleted file mode 100644 index e52b38551..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/GithubCoordinatorActor.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Akka.Actor; -using Akka.Routing; -using Octokit; - -namespace GithubActors.Actors -{ - /// - /// Actor responsible for publishing data about the results - /// of a github operation - /// - public class GithubCoordinatorActor : ReceiveActor - { - #region Message classes - - public class BeginJob - { - public BeginJob(RepoKey repo) - { - Repo = repo; - } - - public RepoKey Repo { get; private set; } - } - - public class SubscribeToProgressUpdates - { - public SubscribeToProgressUpdates(ActorRef subscriber) - { - Subscriber = subscriber; - } - - public ActorRef Subscriber { get; private set; } - } - - public class PublishUpdate - { - private PublishUpdate() { } - private static readonly PublishUpdate _instance = new PublishUpdate(); - - public static PublishUpdate Instance - { - get { return _instance; } - } - } - - /// - /// Let the subscribers know we failed - /// - public class JobFailed - { - public JobFailed(RepoKey repo) - { - Repo = repo; - } - - public RepoKey Repo { get; private set; } - } - - #endregion - - private ActorRef _githubWorker; - - private RepoKey _currentRepo; - private Dictionary _similarRepos; - private HashSet _subscribers; - private CancellationTokenSource _publishTimer; - private GithubProgressStats _githubProgressStats; - - private bool _receivedInitialUsers = false; - - public GithubCoordinatorActor() - { - Waiting(); - } - - protected override void PreStart() - { - _githubWorker = Context.ActorOf(Props.Create(() => new GithubWorkerActor(GithubClientFactory.GetClient)) - .WithRouter(new RoundRobinPool(10))); - } - - private void Waiting() - { - Receive(job => Sender.Tell(new GithubCommanderActor.AbleToAcceptJob(job.Repo))); - Receive(job => - { - BecomeWorking(job.Repo); - - //kick off the job to query the repo's list of starrers - _githubWorker.Tell(new RetryableQuery(new GithubWorkerActor.QueryStarrers(job.Repo), 4)); - }); - } - - private void BecomeWorking(RepoKey repo) - { - _receivedInitialUsers = false; - _currentRepo = repo; - _subscribers = new HashSet(); - _similarRepos = new Dictionary(); - _publishTimer = new CancellationTokenSource(); - _githubProgressStats = new GithubProgressStats(); - Become(Working); - } - - private void BecomeWaiting() - { - //stop publishing - _publishTimer.Cancel(); - Become(Waiting); - } - - private void Working() - { - //received a downloaded user back from the github worker - Receive(user => - { - _githubProgressStats = _githubProgressStats.UserQueriesFinished(); - foreach (var repo in user.Repos) - { - if (!_similarRepos.ContainsKey(repo.HtmlUrl)) - { - _similarRepos[repo.HtmlUrl] = new SimilarRepo(repo); - } - - //increment the number of people who starred this repo - _similarRepos[repo.HtmlUrl].SharedStarrers++; - } - }); - - Receive(update => - { - //check to see if the job is done - if (_receivedInitialUsers && _githubProgressStats.IsFinished) - { - _githubProgressStats = _githubProgressStats.Finish(); - - //all repos minus forks of the current one - var sortedSimilarRepos = _similarRepos.Values - .Where(x => x.Repo.Name != _currentRepo.Repo).OrderByDescending(x => x.SharedStarrers).ToList(); - foreach (var subscriber in _subscribers) - { - subscriber.Tell(sortedSimilarRepos); - } - BecomeWaiting(); - } - - foreach (var subscriber in _subscribers) - { - subscriber.Tell(_githubProgressStats); - } - }); - - //completed our initial job - we now know how many users we need to query - Receive(users => - { - _receivedInitialUsers = true; - _githubProgressStats = _githubProgressStats.SetExpectedUserCount(users.Length); - - //queue up all of the jobs - foreach (var user in users) - _githubWorker.Tell(new RetryableQuery(new GithubWorkerActor.QueryStarrer(user.Login), 3)); - }); - - Receive(job => Sender.Tell(new GithubCommanderActor.UnableToAcceptJob(job.Repo))); - - Receive(updates => - { - //this is our first subscriber, which means we need to turn publishing on - if (_subscribers.Count == 0) - { - Context.System.Scheduler.Schedule(TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100), - Self, PublishUpdate.Instance, _publishTimer.Token); - } - - _subscribers.Add(updates.Subscriber); - }); - - //query failed, but can be retried - Receive(query => query.CanRetry, query => _githubWorker.Tell(query)); - - //query failed, can't be retried, and it's a QueryStarrers operation - means the entire job failed - Receive(query => !query.CanRetry && query.Query is GithubWorkerActor.QueryStarrers, query => - { - _receivedInitialUsers = true; - foreach (var subscriber in _subscribers) - { - subscriber.Tell(new JobFailed(_currentRepo)); - } - BecomeWaiting(); - }); - - //query failed, can't be retried, and it's a QueryStarrers operation - means individual operation failed - Receive(query => !query.CanRetry && query.Query is GithubWorkerActor.QueryStarrer, query => _githubProgressStats.IncrementFailures()); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Actors/GithubValidatorActor.cs b/src/Unit-3/lesson5/Completed/Actors/GithubValidatorActor.cs deleted file mode 100644 index ebd17e1fd..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/GithubValidatorActor.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Linq; -using Akka.Actor; -using Octokit; - -namespace GithubActors.Actors -{ - /// - /// Actor has one job - ensure that a public repo exists at the specified address - /// - public class GithubValidatorActor : ReceiveActor - { - #region Messages - - public class ValidateRepo - { - public ValidateRepo(string repoUri) - { - RepoUri = repoUri; - } - - public string RepoUri { get; private set; } - } - - public class InvalidRepo - { - public InvalidRepo(string repoUri, string reason) - { - Reason = reason; - RepoUri = repoUri; - } - - public string RepoUri { get; private set; } - - public string Reason { get; private set; } - } - - /// - /// System is unable to process additional repos at this time - /// - public class SystemBusy { } - - /// - /// This is a valid repository - /// - public class RepoIsValid - { - /* - * Using singleton pattern here since it's a stateless message. - * - * Considered to be a good practice to eliminate unnecessary garbage collection, - * and it's used internally inside Akka.NET for similar scenarios. - */ - private RepoIsValid() { } - private static readonly RepoIsValid _instance = new RepoIsValid(); - public static RepoIsValid Instance { get { return _instance; } } - } - - #endregion - - private readonly IGitHubClient _gitHubClient; - - public GithubValidatorActor(IGitHubClient gitHubClient) - { - _gitHubClient = gitHubClient; - ReadyToValidate(); - } - - private void ReadyToValidate() - { - //Outright invalid URLs - Receive(repo => string.IsNullOrEmpty(repo.RepoUri) || !Uri.IsWellFormedUriString(repo.RepoUri, UriKind.Absolute), - repo => Sender.Tell(new InvalidRepo(repo.RepoUri, "Not a valid absolute URI"))); - - //Repos that at least have a valid absolute URL - Receive(repo => - { - var userOwner = SplitIntoOwnerAndRepo(repo.RepoUri); - //close over the sender in an instance variable - var sender = Sender; - _gitHubClient.Repository.Get(userOwner.Item1, userOwner.Item2).ContinueWith(t => - { - //Rule #1 of async in Akka.NET - turn exceptions into messages your actor understands - if (t.IsCanceled) - { - return new InvalidRepo(repo.RepoUri, "Repo lookup timed out"); - } - if (t.IsFaulted) - { - return new InvalidRepo(repo.RepoUri, t.Exception != null ? t.Exception.GetBaseException().Message : "Unknown Octokit error"); - } - - return t.Result; - }).PipeTo(Self, sender); - }); - - // something went wrong while querying github, sent to ourselves via PipeTo - // however - Sender gets preserved on the call, so it's safe to use Forward here. - Receive(repo => Sender.Forward(repo)); - - // Octokit was able to retrieve this repository - Receive(repository => - { - //ask the GithubCommander if we can accept this job - Context.ActorSelection(ActorPaths.GithubCommanderActor.Path).Tell(new GithubCommanderActor.CanAcceptJob(new RepoKey(repository.Owner.Login, repository.Name))); - }); - - - /* REPO is valid, but can we process it at this time? */ - - //yes - Receive(job => Context.ActorSelection(ActorPaths.MainFormActor.Path).Tell(job)); - - //no - Receive(job => Context.ActorSelection(ActorPaths.MainFormActor.Path).Tell(job)); - } - - public static Tuple SplitIntoOwnerAndRepo(string repoUri) - { - var split = new Uri(repoUri, UriKind.Absolute).PathAndQuery.TrimEnd('/').Split('/').Reverse().ToList(); //uri path without trailing slash - return Tuple.Create(split[1], split[0]); //User, Repo - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Actors/GithubWorkerActor.cs b/src/Unit-3/lesson5/Completed/Actors/GithubWorkerActor.cs deleted file mode 100644 index 13101bf2c..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/GithubWorkerActor.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Akka.Actor; -using Octokit; - -namespace GithubActors.Actors -{ - /// - /// Individual actor responsible for querying the Github API - /// - public class GithubWorkerActor : ReceiveActor - { - #region Message classes - - public class QueryStarrers - { - public QueryStarrers(RepoKey key) - { - Key = key; - } - - public RepoKey Key { get; private set; } - } - - /// - /// Query an individual starrer - /// - public class QueryStarrer - { - public QueryStarrer(string login) - { - Login = login; - } - - public string Login { get; private set; } - } - - public class StarredReposForUser - { - public StarredReposForUser(string login, IEnumerable repos) - { - Repos = repos; - Login = login; - } - - public string Login { get; private set; } - - public IEnumerable Repos { get; private set; } - } - - #endregion - - private IGitHubClient _gitHubClient; - private readonly Func _gitHubClientFactory; - - public GithubWorkerActor(Func gitHubClientFactory) - { - _gitHubClientFactory = gitHubClientFactory; - InitialReceives(); - } - - protected override void PreStart() - { - _gitHubClient = _gitHubClientFactory(); - } - - private void InitialReceives() - { - //query an individual starrer - Receive(query => query.Query is QueryStarrer, query => - { - // ReSharper disable once PossibleNullReferenceException (we know from the previous IS statement that this is not null) - var starrer = (query.Query as QueryStarrer).Login; - - //close over the Sender in an instance variable - var sender = Sender; - _gitHubClient.Activity.Starring.GetAllForUser(starrer).ContinueWith(tr => - { - //query faulted - if (tr.IsFaulted || tr.IsCanceled) - return query.NextTry(); - //query succeeded - return new StarredReposForUser(starrer, tr.Result); - }).PipeTo(sender); - - }); - - //query all starrers for a repository - Receive(query => query.Query is QueryStarrers, query => - { - // ReSharper disable once PossibleNullReferenceException (we know from the previous IS statement that this is not null) - var starrers = (query.Query as QueryStarrers).Key; - - - //close over the Sender in an instance variable - var sender = Sender; - _gitHubClient.Activity.Starring.GetAllStargazers(starrers.Owner, starrers.Repo) - .ContinueWith(tr => - { - //query faulted - if (tr.IsFaulted || tr.IsCanceled) - return query.NextTry(); - return tr.Result.ToArray(); - }).PipeTo(sender); - - }); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Actors/MainFormActor.cs b/src/Unit-3/lesson5/Completed/Actors/MainFormActor.cs deleted file mode 100644 index ca337b2cc..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/MainFormActor.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Drawing; -using System.Windows.Forms; -using Akka.Actor; - -namespace GithubActors.Actors -{ - /// - /// Actor that runs on the UI thread and handles - /// UI events for - /// - public class MainFormActor : ReceiveActor, WithUnboundedStash - { - #region Messages - - public class LaunchRepoResultsWindow - { - public LaunchRepoResultsWindow(RepoKey repo, ActorRef coordinator) - { - Repo = repo; - Coordinator = coordinator; - } - - public RepoKey Repo { get; private set; } - - public ActorRef Coordinator { get; private set; } - } - - #endregion - - private readonly Label _validationLabel; - - public MainFormActor(Label validationLabel) - { - _validationLabel = validationLabel; - Ready(); - } - - /// - /// State for when we're able to accept new jobs - /// - private void Ready() - { - Receive(repo => - { - Context.ActorSelection(ActorPaths.GithubValidatorActor.Path).Tell(new GithubValidatorActor.ValidateRepo(repo.RepoUri)); - BecomeBusy(repo.RepoUri); - }); - - //launch the window - Receive(window => - { - var form = new RepoResultsForm(window.Coordinator, window.Repo); - form.Show(); - }); - } - - /// - /// Make any necessary URI updates, then switch our state to busy - /// - private void BecomeBusy(string repoUrl) - { - _validationLabel.Visible = true; - _validationLabel.Text = string.Format("Validating {0}...", repoUrl); - _validationLabel.ForeColor = Color.Gold; - Become(Busy); - } - - /// - /// State for when we're currently processing a job - /// - private void Busy() - { - Receive(valid => BecomeReady("Valid!")); - Receive(invalid => BecomeReady(invalid.Reason, false)); - //yes - Receive(job => BecomeReady(string.Format("{0}/{1} is a valid repo, but system can't accept additional jobs", job.Repo.Owner, job.Repo.Repo), false)); - - //no - Receive(job => BecomeReady(string.Format("{0}/{1} is a valid repo - starting job!", job.Repo.Owner, job.Repo.Repo))); - Receive(window => Stash.Stash()); - } - - private void BecomeReady(string message, bool isValid = true) - { - _validationLabel.Text = message; - _validationLabel.ForeColor = isValid ? Color.Green : Color.Red; - Stash.UnstashAll(); - Become(Ready); - } - - public IStash Stash { get; set; } - } -} diff --git a/src/Unit-3/lesson5/Completed/Actors/RepoResultsActor.cs b/src/Unit-3/lesson5/Completed/Actors/RepoResultsActor.cs deleted file mode 100644 index ff49d5c51..000000000 --- a/src/Unit-3/lesson5/Completed/Actors/RepoResultsActor.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; -using System.Windows.Forms; -using Akka.Actor; - -namespace GithubActors.Actors -{ - /// - /// Actor responsible for printing the results and progress from a - /// onto a (runs on the UI thread) - /// - public class RepoResultsActor : ReceiveActor - { - private DataGridView _userDg; - private ToolStripStatusLabel _statusLabel; - private ToolStripProgressBar _progressBar; - - private bool _hasSetProgress = false; - - public RepoResultsActor(DataGridView userDg, ToolStripStatusLabel statusLabel, ToolStripProgressBar progressBar) - { - _userDg = userDg; - _statusLabel = statusLabel; - _progressBar = progressBar; - InitialReceives(); - } - - private void InitialReceives() - { - //progress update - Receive(stats => - { - //time to start animating the progress bar - if (!_hasSetProgress && stats.ExpectedUsers > 0) - { - _progressBar.Minimum = 0; - _progressBar.Step = 1; - _progressBar.Maximum = stats.ExpectedUsers; - _progressBar.Value = stats.UsersThusFar; - _progressBar.Visible = true; - _statusLabel.Visible = true; - } - - _statusLabel.Text = string.Format("{0} out of {1} users ({2} failures) [{3} elapsed]", - stats.UsersThusFar, stats.ExpectedUsers, stats.QueryFailures, stats.Elapsed); - _progressBar.Value = stats.UsersThusFar + stats.QueryFailures; - }); - - //user update - Receive>(repos => - { - foreach (var similarRepo in repos) - { - var repo = similarRepo.Repo; - var row = new DataGridViewRow(); - row.CreateCells(_userDg); - row.Cells[0].Value = repo.Owner.Login; - row.Cells[1].Value = repo.Name; - row.Cells[2].Value = repo.HtmlUrl; - row.Cells[3].Value = similarRepo.SharedStarrers; - row.Cells[4].Value = repo.SubscribersCount; - row.Cells[5].Value = repo.StargazersCount; - row.Cells[6].Value = repo.ForksCount; - _userDg.Rows.Add(row); - } - }); - - //critical failure, like not being able to connect to Github - Receive(failed => - { - _progressBar.Visible = true; - _progressBar.ForeColor = Color.Red; - _progressBar.Maximum = 1; - _progressBar.Value = 1; - _statusLabel.Visible = true; - _statusLabel.Text = string.Format("Failed to gather data for Github repository {0} / {1}", - failed.Repo.Owner, failed.Repo.Repo); - }); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/App.config b/src/Unit-3/lesson5/Completed/App.config index dae86f4a5..cdc79419a 100644 --- a/src/Unit-3/lesson5/Completed/App.config +++ b/src/Unit-3/lesson5/Completed/App.config @@ -10,18 +10,18 @@ ] +[] +[] +[] +[] +[] +[] +[] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [] +[] +[] + +do + () \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/GithubActors.csproj b/src/Unit-3/lesson5/Completed/GithubActors.csproj deleted file mode 100644 index e20bb20ac..000000000 --- a/src/Unit-3/lesson5/Completed/GithubActors.csproj +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Debug - AnyCPU - {93D0C836-98C9-45D1-B269-81220060DBB4} - WinExe - Properties - GithubActors - GithubActors - v4.5 - 512 - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - packages\Akka.0.8.0\lib\net45\Akka.dll - - - False - packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll - - - packages\Octokit.0.7.2\lib\net45\Octokit.dll - - - - - - - - - - - - - - - - - - - - - - Form - - - GithubAuth.cs - - - - - - - - - Form - - - LauncherForm.cs - - - - - Form - - - RepoResultsForm.cs - - - GithubAuth.cs - - - LauncherForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - RepoResultsForm.cs - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/GithubActors.fsproj b/src/Unit-3/lesson5/Completed/GithubActors.fsproj new file mode 100644 index 000000000..20c7fee2c --- /dev/null +++ b/src/Unit-3/lesson5/Completed/GithubActors.fsproj @@ -0,0 +1,115 @@ + + + + + Debug + AnyCPU + 2.0 + 8f96bd67-3bc5-4702-b6d4-a0031b40add5 + WinExe + GithubActors + GithubActors + v4.5 + true + 4.3.1.0 + GithubActors + + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + AnyCPU + bin\Debug\GithubActors.XML + true + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + AnyCPU + bin\Release\GithubActors.XML + true + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + + + + + + + + + + + + + packages\Akka.1.2.0\lib\net45\Akka.dll + + + packages\Akka.FSharp.1.2.0\lib\net45\Akka.FSharp.dll + + + + packages\FSPowerPack.Core.Community.3.0.0.0\Lib\Net40\FSharp.PowerPack.dll + + + packages\FSPowerPack.Linq.Community.3.0.0.0\Lib\Net40\FSharp.PowerPack.Linq.dll + + + packages\FsPickler.1.3.7\lib\net40\FsPickler.dll + + + + True + + + packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + + + packages\Octokit.0.21.1\lib\net45\Octokit.dll + + + + packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + + + + + + + \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/GithubActors.sln b/src/Unit-3/lesson5/Completed/GithubActors.sln index 3116f87b6..96648833c 100644 --- a/src/Unit-3/lesson5/Completed/GithubActors.sln +++ b/src/Unit-3/lesson5/Completed/GithubActors.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GithubActors", "GithubActors.csproj", "{93D0C836-98C9-45D1-B269-81220060DBB4}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "GithubActors", "GithubActors.fsproj", "{8F96BD67-3BC5-4702-B6D4-A0031B40ADD5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +11,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {93D0C836-98C9-45D1-B269-81220060DBB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93D0C836-98C9-45D1-B269-81220060DBB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93D0C836-98C9-45D1-B269-81220060DBB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93D0C836-98C9-45D1-B269-81220060DBB4}.Release|Any CPU.Build.0 = Release|Any CPU + {8F96BD67-3BC5-4702-B6D4-A0031B40ADD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F96BD67-3BC5-4702-B6D4-A0031B40ADD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F96BD67-3BC5-4702-B6D4-A0031B40ADD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F96BD67-3BC5-4702-B6D4-A0031B40ADD5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Unit-3/lesson5/Completed/GithubAuth.Designer.cs b/src/Unit-3/lesson5/Completed/GithubAuth.Designer.cs deleted file mode 100644 index ad4b01a6f..000000000 --- a/src/Unit-3/lesson5/Completed/GithubAuth.Designer.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace GithubActors -{ - partial class GithubAuth - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.label1 = new System.Windows.Forms.Label(); - this.tbOAuth = new System.Windows.Forms.TextBox(); - this.lblAuthStatus = new System.Windows.Forms.Label(); - this.linkGhLabel = new System.Windows.Forms.LinkLabel(); - this.btnAuthenticate = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.Location = new System.Drawing.Point(12, 9); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(172, 18); - this.label1.TabIndex = 0; - this.label1.Text = "GitHub Access Token"; - // - // tbOAuth - // - this.tbOAuth.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbOAuth.Location = new System.Drawing.Point(190, 6); - this.tbOAuth.Name = "tbOAuth"; - this.tbOAuth.Size = new System.Drawing.Size(379, 24); - this.tbOAuth.TabIndex = 1; - // - // lblAuthStatus - // - this.lblAuthStatus.AutoSize = true; - this.lblAuthStatus.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblAuthStatus.Location = new System.Drawing.Point(187, 33); - this.lblAuthStatus.Name = "lblAuthStatus"; - this.lblAuthStatus.Size = new System.Drawing.Size(87, 18); - this.lblAuthStatus.TabIndex = 2; - this.lblAuthStatus.Text = "lblGHStatus"; - this.lblAuthStatus.Visible = false; - // - // linkGhLabel - // - this.linkGhLabel.AutoSize = true; - this.linkGhLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.linkGhLabel.Location = new System.Drawing.Point(148, 128); - this.linkGhLabel.Name = "linkGhLabel"; - this.linkGhLabel.Size = new System.Drawing.Size(273, 18); - this.linkGhLabel.TabIndex = 3; - this.linkGhLabel.Text = "How to get a GitHub Access Token"; - this.linkGhLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkGhLabel_LinkClicked); - // - // btnAuthenticate - // - this.btnAuthenticate.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.btnAuthenticate.Location = new System.Drawing.Point(214, 81); - this.btnAuthenticate.Name = "btnAuthenticate"; - this.btnAuthenticate.Size = new System.Drawing.Size(136, 32); - this.btnAuthenticate.TabIndex = 4; - this.btnAuthenticate.Text = "Authenticate"; - this.btnAuthenticate.UseVisualStyleBackColor = true; - this.btnAuthenticate.Click += new System.EventHandler(this.btnAuthenticate_Click); - // - // GithubAuth - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(582, 155); - this.Controls.Add(this.btnAuthenticate); - this.Controls.Add(this.linkGhLabel); - this.Controls.Add(this.lblAuthStatus); - this.Controls.Add(this.tbOAuth); - this.Controls.Add(this.label1); - this.Name = "GithubAuth"; - this.Text = "Sign in to GitHub"; - this.Load += new System.EventHandler(this.GithubAuth_Load); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label label1; - private System.Windows.Forms.TextBox tbOAuth; - private System.Windows.Forms.Label lblAuthStatus; - private System.Windows.Forms.LinkLabel linkGhLabel; - private System.Windows.Forms.Button btnAuthenticate; - } -} \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/GithubAuth.cs b/src/Unit-3/lesson5/Completed/GithubAuth.cs deleted file mode 100644 index 872723e22..000000000 --- a/src/Unit-3/lesson5/Completed/GithubAuth.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; -using Akka.Actor; -using GithubActors.Actors; - -namespace GithubActors -{ - public partial class GithubAuth : Form - { - private ActorRef _authActor; - - public GithubAuth() - { - InitializeComponent(); - } - - private void GithubAuth_Load(object sender, EventArgs e) - { - linkGhLabel.Links.Add(new LinkLabel.Link() { LinkData = "https://help.github.com/articles/creating-an-access-token-for-command-line-use/" }); - _authActor = - Program.GithubActors.ActorOf(Props.Create(() => new GithubAuthenticationActor(lblAuthStatus, this)), ActorPaths.GithubAuthenticatorActor.Name); - } - - private void linkGhLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - var link = e.Link.LinkData as string; - if (link != null) - { - //Send the URL to the operating system via windows shell - Process.Start(link); - } - } - - private void btnAuthenticate_Click(object sender, EventArgs e) - { - _authActor.Tell(new GithubAuthenticationActor.Authenticate(tbOAuth.Text)); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/GithubAuth.resx b/src/Unit-3/lesson5/Completed/GithubAuth.resx deleted file mode 100644 index 61bc649ba..000000000 --- a/src/Unit-3/lesson5/Completed/GithubAuth.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - True - - \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/GithubAuthForm.fs b/src/Unit-3/lesson5/Completed/GithubAuthForm.fs new file mode 100644 index 000000000..8295b1eb6 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/GithubAuthForm.fs @@ -0,0 +1,44 @@ +namespace GithubActors + +open System +open System.Drawing +open System.Windows.Forms +open System.Diagnostics +open Akka.FSharp + +[] +module GithubAuthForm = + let boldFont = new Font("Microsoft Sans Serif", 11.25f, FontStyle.Bold, GraphicsUnit.Point) + let regularFont = new Font("Microsoft Sans Serif", 11.25f, FontStyle.Regular, GraphicsUnit.Point) + + let form = new Form(Name = "GithubAuthForm", Visible = true, Text = "Sign in to GitHub", AutoScaleDimensions = SizeF(6.F, 13.F), AutoScaleMode = AutoScaleMode.Font, ClientSize = Size(582, 155)) + let lblAccessToken = new Label(Name = "lblAccessToken", Text = "GitHub Access Token", Size = Size(172, 18), Location = Point(12, 8), TabIndex = 0, Font = boldFont, AutoSize = true) + let txtOAuth = new TextBox(Name = "txtOAuth", Size = Size(380, 24), Location = Point(190, 6), TabIndex = 1, Font = regularFont) + let lblAuthStatus = new Label(Name = "lblAuthStatus", Text = "status", Size = Size(88, 18), Location = Point(188, 32), TabIndex = 2, Font = regularFont, Visible = false, AutoSize = true) + let lblLinkGitHub = new LinkLabel(Name = "lblLinkGitHub", Text = "How to get a GitHub Access Token", Size = Size(272, 18), Location = Point(148, 128), TabIndex = 3, Font = boldFont, AutoSize = true) + let btnAuthenticate = new Button(Name = "btnAuthenticate", Text = "Authenticate", Size = Size(136, 32), Location = Point(214, 80), TabIndex = 4, Font = boldFont, UseVisualStyleBackColor = true) + + form.SuspendLayout () + + form.Controls.Add lblAccessToken + form.Controls.Add txtOAuth + form.Controls.Add lblAuthStatus + form.Controls.Add lblLinkGitHub + form.Controls.Add btnAuthenticate + + form.ResumeLayout false + + let load () = + let launcherForm = LauncherForm.load () + let authenticator = spawn ActorSystem.githubActors "authenticator" (Actors.githubAuthenticationActor lblAuthStatus form launcherForm) + + lblLinkGitHub.LinkClicked.Add (fun _ -> Process.Start "https://help.github.com/articles/creating-an-access-token-for-command-line-use/" |> ignore) + btnAuthenticate.Click.Add (fun _ -> + if (String.IsNullOrEmpty txtOAuth.Text) + then + lblAuthStatus.Text <- "Please enter a token." + lblAuthStatus.Visible <- true + lblAuthStatus.ForeColor <- Color.Orange + else authenticator - /// Creates instances. - /// - public static class GithubClientFactory - { - /// - /// OAuth token - necessary to generate authenticated requests - /// and achieve non-terrible hourly API rate limit - /// - public static string OAuthToken { get; set; } - - public static GitHubClient GetUnauthenticatedClient() - { - return new GitHubClient(new ProductHeaderValue("AkkaBootcamp-Unit3")); - } - - public static GitHubClient GetClient() - { - return new GitHubClient(new ProductHeaderValue("AkkaBootcamp-Unit3"), new InMemoryCredentialStore(new Credentials(OAuthToken))); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/GithubClientFactory.fs b/src/Unit-3/lesson5/Completed/GithubClientFactory.fs new file mode 100644 index 000000000..4c97e56b3 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/GithubClientFactory.fs @@ -0,0 +1,16 @@ +namespace GithubActors + +module GithubClientFactory = + + open Octokit + open Octokit.Internal + + let mutable oauthToken = "" + + let setOauthToken token = oauthToken <- token + + let getUnauthenticatedClient () = + GitHubClient (ProductHeaderValue "AkkaBootcamp-Unit3") + + let getClient () = + GitHubClient (ProductHeaderValue "AkkaBootcamp-Unit3", InMemoryCredentialStore (Credentials oauthToken)) \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/GithubProgressStats.cs b/src/Unit-3/lesson5/Completed/GithubProgressStats.cs deleted file mode 100644 index 267ce9278..000000000 --- a/src/Unit-3/lesson5/Completed/GithubProgressStats.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using Octokit; - -namespace GithubActors -{ - /// - /// used to sort the list of similar repos - /// - public class SimilarRepo : IComparable - { - public SimilarRepo(Repository repo) - { - Repo = repo; - } - - public Repository Repo { get; private set; } - - public int SharedStarrers { get; set; } - public int CompareTo(SimilarRepo other) - { - return SharedStarrers.CompareTo(other.SharedStarrers); - } - } - - /// - /// Used to report on incremental progress. - /// - /// Immutable. - /// - public class GithubProgressStats - { - public int ExpectedUsers { get; private set; } - public int UsersThusFar { get; private set; } - public int QueryFailures { get; private set; } - public DateTime StartTime { get; private set; } - public DateTime? EndTime { get; private set; } - - public TimeSpan Elapsed - { - get - { - return ((EndTime.HasValue ? EndTime.Value : DateTime.UtcNow) -StartTime); - } - } - - public bool IsFinished - { - get { return ExpectedUsers == UsersThusFar + QueryFailures; } - } - - public GithubProgressStats() - { - StartTime = DateTime.UtcNow; - } - - private GithubProgressStats(DateTime startTime, int expectedUsers, int usersThusFar, int queryFailures, DateTime? endTime) - { - EndTime = endTime; - QueryFailures = queryFailures; - UsersThusFar = usersThusFar; - ExpectedUsers = expectedUsers; - StartTime = startTime; - } - - /// - /// Add users to the running total of - /// - public GithubProgressStats UserQueriesFinished(int delta = 1) - { - return Copy(usersThusFar: UsersThusFar + delta); - } - - /// - /// Set the total - /// - public GithubProgressStats SetExpectedUserCount(int totalExpectedUsers) - { - return Copy(expectedUsers: totalExpectedUsers); - } - - /// - /// Add to the running total - /// - public GithubProgressStats IncrementFailures(int delta = 1) - { - return Copy(queryFailures: QueryFailures + delta); - } - - /// - /// Query is finished! Set's the - /// - public GithubProgressStats Finish() - { - return Copy(endTime: DateTime.UtcNow); - } - - /// - /// Creates a deep copy of the class - /// - public GithubProgressStats Copy(int? expectedUsers = null, int? usersThusFar = null, int? queryFailures = null, - DateTime? startTime = null, DateTime? endTime = null) - { - return new GithubProgressStats(startTime ?? StartTime, expectedUsers ?? ExpectedUsers, usersThusFar ?? UsersThusFar, - queryFailures ?? QueryFailures, endTime ?? EndTime); - } - } -} \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/LauncherForm.Designer.cs b/src/Unit-3/lesson5/Completed/LauncherForm.Designer.cs deleted file mode 100644 index ac0259049..000000000 --- a/src/Unit-3/lesson5/Completed/LauncherForm.Designer.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace GithubActors -{ - partial class LauncherForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.tbRepoUrl = new System.Windows.Forms.TextBox(); - this.lblRepo = new System.Windows.Forms.Label(); - this.lblIsValid = new System.Windows.Forms.Label(); - this.btnLaunch = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // tbRepoUrl - // - this.tbRepoUrl.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.tbRepoUrl.Location = new System.Drawing.Point(95, 13); - this.tbRepoUrl.Name = "tbRepoUrl"; - this.tbRepoUrl.Size = new System.Drawing.Size(455, 24); - this.tbRepoUrl.TabIndex = 0; - // - // lblRepo - // - this.lblRepo.AutoSize = true; - this.lblRepo.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblRepo.Location = new System.Drawing.Point(3, 16); - this.lblRepo.Name = "lblRepo"; - this.lblRepo.Size = new System.Drawing.Size(86, 18); - this.lblRepo.TabIndex = 1; - this.lblRepo.Text = "Repo URL"; - // - // lblIsValid - // - this.lblIsValid.AutoSize = true; - this.lblIsValid.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblIsValid.Location = new System.Drawing.Point(95, 44); - this.lblIsValid.Name = "lblIsValid"; - this.lblIsValid.Size = new System.Drawing.Size(46, 18); - this.lblIsValid.TabIndex = 2; - this.lblIsValid.Text = "label1"; - this.lblIsValid.Visible = false; - // - // btnLaunch - // - this.btnLaunch.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.btnLaunch.Location = new System.Drawing.Point(218, 90); - this.btnLaunch.Name = "btnLaunch"; - this.btnLaunch.Size = new System.Drawing.Size(142, 37); - this.btnLaunch.TabIndex = 3; - this.btnLaunch.Text = "GO"; - this.btnLaunch.UseVisualStyleBackColor = true; - this.btnLaunch.Click += new System.EventHandler(this.btnLaunch_Click); - // - // LauncherForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(562, 151); - this.Controls.Add(this.btnLaunch); - this.Controls.Add(this.lblIsValid); - this.Controls.Add(this.lblRepo); - this.Controls.Add(this.tbRepoUrl); - this.Name = "LauncherForm"; - this.Text = "Who Starred This Repo?"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.LauncherForm_FormClosing); - this.Load += new System.EventHandler(this.MainForm_Load); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.TextBox tbRepoUrl; - private System.Windows.Forms.Label lblRepo; - private System.Windows.Forms.Label lblIsValid; - private System.Windows.Forms.Button btnLaunch; - } -} - diff --git a/src/Unit-3/lesson5/Completed/LauncherForm.cs b/src/Unit-3/lesson5/Completed/LauncherForm.cs deleted file mode 100644 index ff2361dcc..000000000 --- a/src/Unit-3/lesson5/Completed/LauncherForm.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Windows.Forms; -using Akka.Actor; -using GithubActors.Actors; - -namespace GithubActors -{ - public partial class LauncherForm : Form - { - private ActorRef _mainFormActor; - - public LauncherForm() - { - InitializeComponent(); - } - - private void MainForm_Load(object sender, EventArgs e) - { - /* INITIALIZE ACTORS */ - _mainFormActor = Program.GithubActors.ActorOf(Props.Create(() => new MainFormActor(lblIsValid)), ActorPaths.MainFormActor.Name); - Program.GithubActors.ActorOf(Props.Create(() => new GithubValidatorActor(GithubClientFactory.GetClient())), ActorPaths.GithubValidatorActor.Name); - Program.GithubActors.ActorOf(Props.Create(() => new GithubCommanderActor()), - ActorPaths.GithubCommanderActor.Name); - } - - private void btnLaunch_Click(object sender, EventArgs e) - { - _mainFormActor.Tell(new ProcessRepo(tbRepoUrl.Text)); - } - - private void LauncherForm_FormClosing(object sender, FormClosingEventArgs e) - { - Application.Exit(); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/LauncherForm.fs b/src/Unit-3/lesson5/Completed/LauncherForm.fs new file mode 100644 index 000000000..64583fddc --- /dev/null +++ b/src/Unit-3/lesson5/Completed/LauncherForm.fs @@ -0,0 +1,39 @@ +namespace GithubActors + +open System.Drawing +open System.Windows.Forms +open Akka.FSharp + +[] +module LauncherForm = + let boldFont = new Font("Microsoft Sans Serif", 11.25f, FontStyle.Bold, GraphicsUnit.Point) + let regularFont = new Font("Microsoft Sans Serif", 11.25f, FontStyle.Regular, GraphicsUnit.Point) + + let form = new Form(Name = "LauncherForm", Visible = false, Text = "Launcher", AutoScaleDimensions = SizeF(6.F, 13.F), AutoScaleMode = AutoScaleMode.Font, ClientSize = Size(582, 155)) + let lblRepo = new Label(Name = "lblRepo", Text = "Repo URL", Size = Size(86, 18), Location = Point(3, 16), TabIndex = 0, Font = boldFont, AutoSize = true) + let txtRepoUrl = new TextBox(Name = "txtRepoUrl", Size = Size(456, 24), Location = Point(96, 13), TabIndex = 1, Font = regularFont) + let lblIsValid = new Label(Name = "lblIsValid", Text = "isValid", Size = Size(46, 18), Location = Point(96, 44), TabIndex = 2, Font = regularFont, Visible = false, AutoSize = true) + let btnLaunch = new Button(Name = "btnLaunch", Text = "GO", Size = Size(142, 32), Location = Point(218, 90), TabIndex = 4, Font = boldFont, UseVisualStyleBackColor = true) + + form.SuspendLayout () + + form.Controls.Add lblRepo + form.Controls.Add txtRepoUrl + form.Controls.Add lblIsValid + form.Controls.Add btnLaunch + + form.ResumeLayout false + + let load () = + + let createRepoResultsForm (repoKey: RepoKey) coordinator = + RepoResultsForm.createNew repoKey coordinator + + let mainFormActor = spawn ActorSystem.githubActors "mainform" (Actors.mainFormActor lblIsValid createRepoResultsForm) + let validator = spawn ActorSystem.githubActors "validator" (Actors.githubValidatorActor GithubClientFactory.getClient) + let commander = spawn ActorSystem.githubActors "commander" (Actors.githubCommanderActor) + + btnLaunch.Click.Add (fun _ -> mainFormActor Application.Exit ()) + + form \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/LauncherForm.resx b/src/Unit-3/lesson5/Completed/LauncherForm.resx deleted file mode 100644 index 1af7de150..000000000 --- a/src/Unit-3/lesson5/Completed/LauncherForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/Messages.cs b/src/Unit-3/lesson5/Completed/Messages.cs deleted file mode 100644 index c3e5c1f77..000000000 --- a/src/Unit-3/lesson5/Completed/Messages.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace GithubActors -{ - /// - /// Begin processing a new Github repository for analysis - /// - public class ProcessRepo - { - public ProcessRepo(string repoUri) - { - RepoUri = repoUri; - } - - public string RepoUri { get; private set; } - } - - public class RepoKey - { - public RepoKey(string owner, string repo) - { - Repo = repo; - Owner = owner; - } - - public string Owner { get; private set; } - - public string Repo { get; private set; } - } - - - public class RetryableQuery - { - public RetryableQuery(object query, int allowableTries) : this(query, allowableTries, 0) - { - } - - private RetryableQuery(object query, int allowableTries, int currentAttempt) - { - AllowableTries = allowableTries; - Query = query; - CurrentAttempt = currentAttempt; - } - - - public object Query { get; private set; } - - public int AllowableTries { get; private set; } - - public int CurrentAttempt { get; private set; } - - public bool CanRetry { get { return RemainingTries > 0; } } - public int RemainingTries { get { return AllowableTries - CurrentAttempt; } } - - public RetryableQuery NextTry() - { - return new RetryableQuery(Query, AllowableTries, CurrentAttempt+1); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Messages.fs b/src/Unit-3/lesson5/Completed/Messages.fs new file mode 100644 index 000000000..7e3fc3946 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/Messages.fs @@ -0,0 +1,109 @@ +namespace GithubActors + +open System +open Akka.Actor + +[] +module GeneralTypes = + + type RepoKey = { Owner: string; Repo: string } + + // RetryableQuery section + type RetryableQuery = { + Query: obj + AllowableTries: int + CurrentAttempt: int + } with + member this.RemainingTries = this.AllowableTries - this.CurrentAttempt + member this.CanRetry = this.RemainingTries > 0 + + let nextTry (retryableQuery: RetryableQuery) = + { retryableQuery with CurrentAttempt = retryableQuery.CurrentAttempt + 1 } + + // SimilarRepo section + type SimilarRepo = { + Repo: Octokit.Repository + SharedStarrers: int + } + + let increaseSharedStarrers similarRepo = + { similarRepo with SharedStarrers = similarRepo.SharedStarrers + 1 } + + // GithubProgressStats section + type GithubProgressStats = { + ExpectedUsers: int + UsersThusFar: int + QueryFailures: int + StartTime: DateTime + EndTime: DateTime option + } with + member this.Elapsed = + match this.EndTime with + | Some endTime -> endTime - this.StartTime + | None -> DateTime.UtcNow - this.StartTime + member this.IsFinished = + this.ExpectedUsers = (this.UsersThusFar + this.QueryFailures) + + let getDefaultStats () = + { + ExpectedUsers = 0 + UsersThusFar = 0 + QueryFailures = 0 + StartTime = DateTime.UtcNow + EndTime = None + } + + let userQueriesFinished stats delta = + { stats with UsersThusFar = stats.UsersThusFar + delta } + + let setExpectedUserCount stats totalExpectedUsers = + { stats with ExpectedUsers = totalExpectedUsers } + + let incrementFailures stats delta = + { stats with QueryFailures = stats.QueryFailures + delta } + + let finish stats = + { stats with EndTime = Some DateTime.UtcNow } + + // WorkerSettings section + type WorkerSettings = { + ReceivedInitialUsers: bool + CurrentRepo: RepoKey + Subscribers: System.Collections.Generic.HashSet + SimilarRepos: System.Collections.Generic.Dictionary + GithubProgressStats: GithubProgressStats + PublishTimer: Cancelable + } + +[] +module Messages = + + type AuthenticationMessage = + | Authenticate of oauthToken: string + | AuthenticationFailed + | AuthenticationCancelled + | AuthenticationSuccess + + type GithubActorMessage = + // Job-related + | AbleToAcceptJob of repoKey: RepoKey + | UnableToAcceptJob of repoKey: RepoKey + | CanAcceptJob of repoKey: RepoKey + | BeginJob of repoKey: RepoKey + | JobFailed of repoKey: RepoKey + // Repo-related + | ValidateRepo of repoUri: string + | ValidRepo of repo: Octokit.Repository + | InvalidRepo of repoUri: string * reason: string + | ProcessRepo of repoUri: string + // Query-related + | LaunchRepoResultsWindow of repoKey: RepoKey * coordinator: IActorRef + | StarredReposForUser of login: string * repos: Octokit.Repository seq + | PublishUpdate + | SubscribeToProgressUpdates of subscriber: IActorRef + | RetryableQuery of query: RetryableQuery + | QueryStarrers of repoKey: RepoKey + | QueryStarrer of login: string + | UsersToQuery of users: Octokit.User array + | GithubProgressStats of stats: GithubProgressStats + | SimilarRepos of repos: SimilarRepo seq \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/Program.cs b/src/Unit-3/lesson5/Completed/Program.cs deleted file mode 100644 index fb7b9b28d..000000000 --- a/src/Unit-3/lesson5/Completed/Program.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Configuration; -using System.Windows.Forms; -using Akka.Actor; -using Akka.Configuration.Hocon; - -namespace GithubActors -{ - static class Program - { - /// - /// ActorSystem we'llbe using to collect and process data - /// from Github using their official .NET SDK, Octokit - /// - public static ActorSystem GithubActors; - - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - var section = (AkkaConfigurationSection)ConfigurationManager.GetSection("akka"); - var config = section.AkkaConfig; - GithubActors = ActorSystem.Create("GithubActors", config); - - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new GithubAuth()); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Program.fs b/src/Unit-3/lesson5/Completed/Program.fs new file mode 100644 index 000000000..e4296d150 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/Program.fs @@ -0,0 +1,11 @@ +module Program + +open System +open System.Windows.Forms +open GithubActors + +Application.EnableVisualStyles () +Application.SetCompatibleTextRenderingDefault false + +[] +do Application.Run (GithubAuthForm.load ()) \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/Properties/AssemblyInfo.cs b/src/Unit-3/lesson5/Completed/Properties/AssemblyInfo.cs deleted file mode 100644 index 32c2a78eb..000000000 --- a/src/Unit-3/lesson5/Completed/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GithubActors")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("GithubActors")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4be30889-fb62-4047-8f7d-bbf5c70e2a68")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Unit-3/lesson5/Completed/Properties/Resources.Designer.cs b/src/Unit-3/lesson5/Completed/Properties/Resources.Designer.cs deleted file mode 100644 index 68d8e89af..000000000 --- a/src/Unit-3/lesson5/Completed/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.34014 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace GithubActors.Properties -{ - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GithubActors.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Properties/Resources.resx b/src/Unit-3/lesson5/Completed/Properties/Resources.resx deleted file mode 100644 index af7dbebba..000000000 --- a/src/Unit-3/lesson5/Completed/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/Properties/Settings.Designer.cs b/src/Unit-3/lesson5/Completed/Properties/Settings.Designer.cs deleted file mode 100644 index bbc0bf47a..000000000 --- a/src/Unit-3/lesson5/Completed/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.34014 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace GithubActors.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/src/Unit-3/lesson5/Completed/Properties/Settings.settings b/src/Unit-3/lesson5/Completed/Properties/Settings.settings deleted file mode 100644 index 39645652a..000000000 --- a/src/Unit-3/lesson5/Completed/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/Unit-3/lesson5/Completed/RepoResultsForm.Designer.cs b/src/Unit-3/lesson5/Completed/RepoResultsForm.Designer.cs deleted file mode 100644 index 0fa69019b..000000000 --- a/src/Unit-3/lesson5/Completed/RepoResultsForm.Designer.cs +++ /dev/null @@ -1,157 +0,0 @@ -namespace GithubActors -{ - partial class RepoResultsForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.dgUsers = new System.Windows.Forms.DataGridView(); - this.statusStrip1 = new System.Windows.Forms.StatusStrip(); - this.tsProgress = new System.Windows.Forms.ToolStripProgressBar(); - this.tsStatus = new System.Windows.Forms.ToolStripStatusLabel(); - this.Owner = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.RepoName = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.URL = new System.Windows.Forms.DataGridViewLinkColumn(); - this.Shared = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.Watchers = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.Stars = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.Forks = new System.Windows.Forms.DataGridViewTextBoxColumn(); - ((System.ComponentModel.ISupportInitialize)(this.dgUsers)).BeginInit(); - this.statusStrip1.SuspendLayout(); - this.SuspendLayout(); - // - // dgUsers - // - this.dgUsers.AllowUserToOrderColumns = true; - this.dgUsers.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dgUsers.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { - this.Owner, - this.RepoName, - this.URL, - this.Shared, - this.Watchers, - this.Stars, - this.Forks}); - this.dgUsers.Dock = System.Windows.Forms.DockStyle.Fill; - this.dgUsers.Location = new System.Drawing.Point(0, 0); - this.dgUsers.Name = "dgUsers"; - this.dgUsers.Size = new System.Drawing.Size(739, 322); - this.dgUsers.TabIndex = 0; - // - // statusStrip1 - // - this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.tsProgress, - this.tsStatus}); - this.statusStrip1.Location = new System.Drawing.Point(0, 300); - this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(739, 22); - this.statusStrip1.TabIndex = 1; - this.statusStrip1.Text = "statusStrip1"; - // - // tsProgress - // - this.tsProgress.Name = "tsProgress"; - this.tsProgress.Size = new System.Drawing.Size(100, 16); - this.tsProgress.Visible = false; - // - // tsStatus - // - this.tsStatus.Name = "tsStatus"; - this.tsStatus.Size = new System.Drawing.Size(73, 17); - this.tsStatus.Text = "Processing..."; - this.tsStatus.Visible = false; - // - // Owner - // - this.Owner.HeaderText = "Owner"; - this.Owner.Name = "Owner"; - // - // RepoName - // - this.RepoName.HeaderText = "Name"; - this.RepoName.Name = "RepoName"; - // - // URL - // - this.URL.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; - this.URL.HeaderText = "URL"; - this.URL.Name = "URL"; - // - // Shared - // - this.Shared.HeaderText = "SharedStars"; - this.Shared.Name = "Shared"; - // - // Watchers - // - this.Watchers.HeaderText = "Watchers"; - this.Watchers.Name = "Watchers"; - // - // Stars - // - this.Stars.HeaderText = "Stars"; - this.Stars.Name = "Stars"; - // - // Forks - // - this.Forks.HeaderText = "Forks"; - this.Forks.Name = "Forks"; - // - // RepoResultsForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(739, 322); - this.Controls.Add(this.statusStrip1); - this.Controls.Add(this.dgUsers); - this.Name = "RepoResultsForm"; - this.Text = "Repos Similar to {RepoName}"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.RepoResultsForm_FormClosing); - this.Load += new System.EventHandler(this.RepoResultsForm_Load); - ((System.ComponentModel.ISupportInitialize)(this.dgUsers)).EndInit(); - this.statusStrip1.ResumeLayout(false); - this.statusStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.DataGridView dgUsers; - private System.Windows.Forms.StatusStrip statusStrip1; - private System.Windows.Forms.ToolStripProgressBar tsProgress; - private System.Windows.Forms.ToolStripStatusLabel tsStatus; - private System.Windows.Forms.DataGridViewTextBoxColumn Owner; - private System.Windows.Forms.DataGridViewTextBoxColumn RepoName; - private System.Windows.Forms.DataGridViewLinkColumn URL; - private System.Windows.Forms.DataGridViewTextBoxColumn Shared; - private System.Windows.Forms.DataGridViewTextBoxColumn Watchers; - private System.Windows.Forms.DataGridViewTextBoxColumn Stars; - private System.Windows.Forms.DataGridViewTextBoxColumn Forks; - } -} \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/RepoResultsForm.cs b/src/Unit-3/lesson5/Completed/RepoResultsForm.cs deleted file mode 100644 index ee4dad6e2..000000000 --- a/src/Unit-3/lesson5/Completed/RepoResultsForm.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Windows.Forms; -using Akka.Actor; -using GithubActors.Actors; - -namespace GithubActors -{ - public partial class RepoResultsForm : Form - { - private ActorRef _formActor; - private ActorRef _githubCoordinator; - private RepoKey _repo; - - public RepoResultsForm(ActorRef githubCoordinator, RepoKey repo) - { - _githubCoordinator = githubCoordinator; - _repo = repo; - InitializeComponent(); - } - - private void RepoResultsForm_Load(object sender, System.EventArgs e) - { - _formActor = - Program.GithubActors.ActorOf( - Props.Create(() => new RepoResultsActor(dgUsers, tsStatus, tsProgress)) - .WithDispatcher("akka.actor.synchronized-dispatcher")); //run on the UI thread - - Text = string.Format("Repos Similar to {0} / {1}", _repo.Owner, _repo.Repo); - - //start subscribing to updates - _githubCoordinator.Tell(new GithubCoordinatorActor.SubscribeToProgressUpdates(_formActor)); - } - - private void RepoResultsForm_FormClosing(object sender, FormClosingEventArgs e) - { - //kill the form actor - _formActor.Tell(PoisonPill.Instance); - } - } -} diff --git a/src/Unit-3/lesson5/Completed/RepoResultsForm.fs b/src/Unit-3/lesson5/Completed/RepoResultsForm.fs new file mode 100644 index 000000000..8b4833485 --- /dev/null +++ b/src/Unit-3/lesson5/Completed/RepoResultsForm.fs @@ -0,0 +1,83 @@ +namespace GithubActors + +open System +open System.Drawing +open System.Windows.Forms +open System.ComponentModel +open Akka.Actor +open Akka.FSharp + +[] +module RepoResultsForm = + + let generateRandomActorName () = + Guid.NewGuid().ToString().[0..7] + |> sprintf "repoResults%s" + + let createNew (repoKey: RepoKey) (coordinator: IActorRef) = + + let formTitle = sprintf "Repos similar to %s / %s" repoKey.Owner repoKey.Repo + let form = new Form(Name = "RepoResultsForm", Visible = false, Text = formTitle, AutoScaleDimensions = SizeF(6.F, 13.F), AutoScaleMode = AutoScaleMode.Font, ClientSize = Size(740, 322)) + + let dgUsers = new DataGridView() + let statusStrip = new StatusStrip() + let progressBar = new ToolStripProgressBar() + let lblStatus = new ToolStripStatusLabel() + + (dgUsers :> ISupportInitialize).BeginInit () + statusStrip.SuspendLayout () + form.SuspendLayout () + + dgUsers.Name <- "dgUsers" + dgUsers.AllowUserToOrderColumns <- true + dgUsers.ColumnHeadersHeightSizeMode <- DataGridViewColumnHeadersHeightSizeMode.AutoSize + let columns : DataGridViewColumn [] = [| + new DataGridViewTextBoxColumn(HeaderText = "Owner", Name = "colOwner") // owner + new DataGridViewTextBoxColumn(HeaderText = "Repo Name", Name = "colRepoName") // repo name + new DataGridViewLinkColumn(HeaderText = "URL", Name = "colUrl", AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill) // URL + new DataGridViewTextBoxColumn(HeaderText = "Shared", Name = "colShared") // shared + new DataGridViewTextBoxColumn(HeaderText = "Issues", Name = "colIssues") // issues + new DataGridViewTextBoxColumn(HeaderText = "Stars", Name = "colStars") // stars + new DataGridViewTextBoxColumn(HeaderText = "Forks", Name = "colForks") // forks + |] + dgUsers.Columns.AddRange columns + dgUsers.Dock <- DockStyle.Fill + dgUsers.TabIndex <- 0 + dgUsers.Size <- Size(740, 322) + dgUsers.Location <- Point(0, 0) + + statusStrip.Name <- "statusStrip" + statusStrip.Text <- "status" + let items : ToolStripItem [] = [| progressBar; lblStatus |] + statusStrip.Items.AddRange items + statusStrip.Location <- Point(0, 300) + statusStrip.Size <- Size(740, 22) + statusStrip.TabIndex <- 1 + + progressBar.Name <- "progressBar" + progressBar.Size <- Size(100, 16) + progressBar.Visible <- false + + lblStatus.Name <- "lblStatus" + lblStatus.Text <- "Processing..." + lblStatus.Size <- Size(72, 16) + lblStatus.Visible <- false + + form.Controls.Add statusStrip + form.Controls.Add dgUsers + + (dgUsers :> ISupportInitialize).EndInit () + statusStrip.ResumeLayout false + statusStrip.PerformLayout () + form.ResumeLayout false + form.PerformLayout () + + let actorName = generateRandomActorName () + let repoResultsActor = spawnOpt ActorSystem.githubActors actorName (Actors.repoResultsActor dgUsers lblStatus progressBar) [SpawnOption.Dispatcher "akka.actor.synchronized-dispatcher"] + coordinator + // + repoResultsActor - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - True - - - True - - - True - - - True - - - True - - - True - - - True - - - 17, 17 - - \ No newline at end of file diff --git a/src/Unit-3/lesson5/Completed/packages.config b/src/Unit-3/lesson5/Completed/packages.config index d9dbe7767..c7fc4b7b2 100644 --- a/src/Unit-3/lesson5/Completed/packages.config +++ b/src/Unit-3/lesson5/Completed/packages.config @@ -1,6 +1,11 @@  - - - + + + + + + + + \ No newline at end of file diff --git a/src/Unit-3/lesson5/README.md b/src/Unit-3/lesson5/README.md index 5bf06a48d..f25101a4e 100644 --- a/src/Unit-3/lesson5/README.md +++ b/src/Unit-3/lesson5/README.md @@ -3,7 +3,7 @@ Wow, look at you! Here we are on our last lesson of Bootcamp together. We want t In this lesson, we'll be going over how to handle timeouts within actors and prevent deadlocks, where one actor is waiting for another indefinitely. -This lesson will show you how to prevent deadlocks by using a `RecieveTimeout`. +This lesson will show you how to prevent deadlocks by using a `ReceiveTimeout`. ## Key Concepts / Background ### What is `ReceiveTimeout`? @@ -21,34 +21,36 @@ Here are some common cases where you may want to use a `ReceiveTimeout`: - To prevent deadlocks where one actor thinks another is doing work ### How do I set up a `ReceiveTimeout`? -You call `Context.SetReceiveTimeout()` and pass it a `TimeSpan`. If that amount of time passes and the actor hasn't received a message, the actor will send itself the `ReceiveTimeout` singleton as a message, e.g. +You call `Context.SetReceiveTimeout` and pass it a `TimeSpan`. If that amount of time passes and the actor hasn't received a message, the actor will send itself the `ReceiveTimeout` singleton as a message, e.g. -```csharp +```fsharp // send ourselves a ReceiveTimeout message if no message within 3 seconds -Context.SetReceiveTimeout(TimeSpan.FromSeconds(3)); +mailbox.Context.SetReceiveTimeout (Nullable(TimeSpan.FromSeconds 3.)) ``` Then, you just need to handle the `ReceiveTimeout` message and take whatever action is appropriate. Let's assume you wanted to shut an actor down after a period of inactivity, and inform its parent. Here's one basic way you could do that: -```csharp -// have actor shut down after a long period of inactivity -Receive(timeout => -{ - // inform parent that shutting down - Context.Parent.Tell(new ImShuttingDown()); +```fsharp +// inside an actor expression +let! message = mailbox.Receive () + +match box message with +| :? ReceiveTimeout as timeout -> + // inform parent that I'm shutting down + mailbox.Context.Parent (job => - { - _coordinator.Tell(job); - _repoJob = job.Repo; - BecomeAsking(); - }); -} -``` +And modify the ready state in githubCommanderActor to look like this: -### Phase 2 - Wire up `ReceiveTimeout` inside `GithubCommanderActor` -We need to set a few calls to `Context.ReceiveTimeout` in order to get it to work properly with our `GithubCommanderActor` when we're inside the `Asking` state. +```fsharp +let rec ready canAcceptJobSender pendingJobReplies = + actor { + let! message = mailbox.Receive () -First, modify the `BecomeAsking` method on the `GithubCommanderActor` to look like this: + match box message with + | :? GithubActorMessage as githubMessage -> + match githubMessage with + | CanAcceptJob repoKey -> + coordinator (new GetRoutees()).Result.Members.Count(); - Become(Asking); + let routees: Routees = coordinator Async.RunSynchronously - // send ourselves a ReceiveTimeout message if no message within 3 seonds - Context.SetReceiveTimeout(TimeSpan.FromSeconds(3)); -} + mailbox.Context.SetReceiveTimeout (Nullable(TimeSpan.FromSeconds 3.)) // set the receive timeout to 3s + return! asking mailbox.Context.Sender (routees.Members.Count ()) + | _ -> return! ready canAcceptJobSender pendingJobReplies + | _ -> return! ready canAcceptJobSender pendingJobReplies + } +// rest of the code... ``` -This means that once the `GithubCommanderActor` enters the `Asking` behavior, it will automatically send itself a `ReceiveTimeout` message if it hasn't received any other message for longer than three seconds. - -Speaking of which, let's add a handler for the `ReceiveTimeout` message type inside the `Asking` method on `GithubCommanderActor`. - +We made 3 changes here: +- first we needed to change the type of messages that `githubCommanderActor` can handle. Now not only handle can it handle `GithubActorMessage`s, it should also handle `ReceiveTimeout` messages. +- we now store the current `repoKey` in our new `currentRepoKey` variable. +- we set the `ReceiveTimeout` property of the actor to 3 seconds. + +This means that once the `githubCommanderActor` enters the `asking` behavior, it will automatically send itself a `ReceiveTimeout` message if it hasn't received any other message for longer than three seconds. + +Speaking of which, let's add a handler for the `ReceiveTimeout` message type inside the `asking` method on `githubCommanderActor`. + +```fsharp +and asking canAcceptJobSender pendingJobReplies = + actor { + let! message = mailbox.Receive () + + match box message with + // The new case to handle ReceiveTimeout messages + | :? ReceiveTimeout as timeout -> + canAcceptJobSender + match githubMessage with + // code that handles GitHubActorMessages (same as before) + | _ -> return! asking canAcceptJobSender pendingJobReplies + } ``` -// add this inside the GithubCommanderActor.Asking method -// means at least one actor failed to respond -Receive(timeout => -{ - _canAcceptJobSender.Tell(new UnableToAcceptJob(_repoJob)); - BecomeReady(); -}); -``` - -We're going to treat every `ReceiveTimeout` as a "busy" signal from one of the `GithubCoordinatorActor` instances, so we'll send ourselves a `UnableToAcceptJob` message every time we receive a `ReceiveTimeout`. - -Once the `GithubCommanderActor` has received all of the replies its expecting and it switches back to its `Ready` state, we need to cancel the `ReceiveTimeout`. - -Modify the `GithubCommanderActor`'s `BecomeReady` method to look like the following: - -```csharp -// modify the GithubCommanderActor.BecomeReady method to read like the following: -private void BecomeReady() -{ - Become(Ready); - Stash.UnstashAll(); - // cancel ReceiveTimeout - Context.SetReceiveTimeout(null); -} +We're going to treat every `ReceiveTimeout` as a "busy" signal from one of the `githubCoordinatorActor` instances so we'll send ourselves a `UnableToAcceptJob` message every time we receive a `ReceiveTimeout`. + +Once the `githubCommanderActor` has received all of the replies its expecting and it switches back to its `Ready` state, we need to cancel the `ReceiveTimeout`. + +Modify the `githubCommanderActor` to cancel `ReceiveTimeout` every time it returns to the `ready` state: + +```fsharp +| :? GithubActorMessage as githubMessage -> + match githubMessage with + | CanAcceptJob repoKey -> + mailbox.Stash () + return! asking canAcceptJobSender pendingJobReplies + | UnableToAcceptJob repoKey -> + let currentPendingJobReplies = pendingJobReplies - 1 + if currentPendingJobReplies = 0 then + canAcceptJobSender + canAcceptJobSender return! asking canAcceptJobSender pendingJobReplies +| _ -> return! asking canAcceptJobSender pendingJobReplies ``` And that's it!