Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
wuriyanto48 committed Sep 24, 2018
0 parents commit 370853b
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 0 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Pocket

#### An Experimental In Memory Key Value written in Elixir built on top of GenServer and TCP protocol

# Note !!!
#### this project is only for the purpose of having fun with Elixir's GenServer and Elixir's Map :stuck_out_tongue_winking_eye:

## Installation

the package can be installed
by adding `pocket` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:pocket, "~> 0.1.0", github: "Bhinneka/pocket"}
]
end
```

## Usage
Run iex
```shell
$ iex
iex(1)> Pocket.start([], [])
```

Open new terminal
```
$ telnet 127.0.0.1 9000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
```

Add new value
```shell
SET a andi
OK
GET a
andi
```


#### Author
Wuriyanto github.com/wuriyanto48

##

### Bhinneka.com 2018
30 changes: 30 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :pocket, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:pocket, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
23 changes: 23 additions & 0 deletions lib/application.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Pocket do
@moduledoc false

use Application

def start(_type, _args) do
import Supervisor.Spec, warn: false

# list options
options = [
ip: Application.get_env(:extcp, :ip, {127,0,0,1}),
port: Application.get_env(:extcp, :port, 9000)
]
# List all child processes to be supervised
children = [
# Starts a worker by calling: Pocket.Worker.start_link(arg)
{Pocket.Server, options},
]

opts = [strategy: :one_for_one, name: Pocket.Supervisor]
Supervisor.start_link(children, opts)
end
end
77 changes: 77 additions & 0 deletions lib/pocket/server.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
defmodule Pocket.Server do
@moduledoc """
Documentation for Pocket.
"""
use GenServer

def start_link(options) do
GenServer.start_link(__MODULE__, options, [])
end

def init(options) do
{:ok, listen_socket} = :gen_tcp.listen(options[:port], [:binary, {:packet, 0}, {:active, true}, {:ip, options[:ip]}])
{:ok, _client} = :gen_tcp.accept(listen_socket)

#initial state with empty Map
db = %{}

{:ok, db}
end

def handle_info({:tcp, client, packet}, db) do
IO.inspect db, label: "Incoming packet"

case handle_input(packet, db) do
{:ok, new_db, res} -> :gen_tcp.send(client, "#{res}\n")
{:noreply, new_db}
{:error, err} -> :gen_tcp.send(client, "Error #{err}")
{:noreply, db}
end

# command = String.split(packet) |> Enum.at(0)
# if command === "SET" || command === "GET" do
# if command === "SET" do
# key = String.split(packet) |> Enum.at(1)
# value = String.split(packet) |> Enum.at(2)
# new_db = Map.put_new(db, key, value)
# :gen_tcp.send(client, "OK\n")
# {:noreply, new_db}
# else
# key = String.split(packet) |> Enum.at(1)
# v = Map.get(db, key)
# :gen_tcp.send(client, "#{v}\n")
# {:noreply, db}
# end
# end
end

def handle_info({:tcp_closed, _socket}, db) do
IO.inspect "socket closed"
{:noreply, db}
end

def handle_info({:tcp_error, socket, reason}, db) do
IO.inspect socket, label: "connection error #{reason}"
{:noreply, db}
end

defp handle_input(packet, db) when is_binary(packet) do
command = String.split(packet) |> Enum.at(0)
if command === "SET" || command === "GET" do
if command === "SET" do
key = String.split(packet) |> Enum.at(1)
value = String.split(packet) |> Enum.at(2)
new_db = Map.put_new(db, key, value)
{:ok, new_db, "OK"}
else
key = String.split(packet) |> Enum.at(1)
v = Map.get(db, key)
{:ok, db, v}
end
end
end

defp handle_input(_, _) do
{:error, "invalid command"}
end
end
28 changes: 28 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Pocket.MixProject do
use Mix.Project

def project do
[
app: :pocket,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
]
end
end
8 changes: 8 additions & 0 deletions test/pocket_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule PocketTest do
use ExUnit.Case
doctest Pocket

test "greets the world" do
assert Pocket.hello() == :world
end
end
1 change: 1 addition & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ExUnit.start()

0 comments on commit 370853b

Please sign in to comment.