moel@348: /*
moel@348:  
moel@348:   This Source Code Form is subject to the terms of the Mozilla Public
moel@348:   License, v. 2.0. If a copy of the MPL was not distributed with this
moel@348:   file, You can obtain one at http://mozilla.org/MPL/2.0/.
moel@348:  
moel@348: 	Copyright (C) 2012 Prince Samuel <prince.samuel@gmail.com>
moel@386:   Copyright (C) 2012 Michael Möller <mmoeller@openhardwaremonitor.org>
moel@348: 
moel@348: */
moel@348: 
moel@348: using System;
moel@386: using System.Drawing;
moel@386: using System.Drawing.Imaging;
moel@386: using System.IO;
moel@386: using System.Net;
moel@386: using System.Reflection;
moel@348: using System.Text;
moel@348: using System.Threading;
moel@348: using OpenHardwareMonitor.GUI;
moel@348: using OpenHardwareMonitor.Hardware;
moel@348: 
moel@386: namespace OpenHardwareMonitor.Utilities {
moel@386: 
moel@386:   public class HttpServer {
moel@386:     private HttpListener listener;
moel@386:     private int listenerPort, nodeCount;
moel@386:     private Thread listenerThread;
moel@386:     private Node root;
moel@386: 
moel@387:     public HttpServer(Node node, int port) {
moel@387:       root = node;
moel@387:       listenerPort = port;
moel@386: 
moel@386:       //JSON node count. 
moel@386:       nodeCount = 0;
moel@387: 
moel@387:       try {
moel@387:         listener = new HttpListener();
moel@387:       } catch (PlatformNotSupportedException) {
moel@387:         listener = null;
moel@387:       }
moel@387:     }
moel@387: 
moel@387:     public bool PlatformNotSupported {
moel@387:       get {
moel@387:         return listener == null;
moel@387:       }
moel@386:     }
moel@386: 
moel@386:     public Boolean StartHTTPListener() {
moel@387:       if (PlatformNotSupported)
moel@387:         return false;
moel@387: 
moel@386:       try {
moel@386:         if (listener.IsListening)
moel@386:           return true;
moel@386: 
moel@386:         string prefix = "http://+:" + listenerPort + "/";
moel@386:         listener.Prefixes.Clear();
moel@386:         listener.Prefixes.Add(prefix);
moel@386:         listener.Start();
moel@386: 
moel@386:         if (listenerThread == null) {
moel@386:           listenerThread = new Thread(HandleRequests);
moel@386:           listenerThread.Start();
moel@386:         }
moel@386:       } catch (Exception) {
moel@386:         return false;
moel@386:       }
moel@386: 
moel@386:       return true;
moel@386:     }
moel@386: 
moel@386:     public Boolean StopHTTPListener() {
moel@387:       if (PlatformNotSupported)
moel@387:         return false;
moel@387: 
moel@386:       try {
moel@386:         listenerThread.Abort();
moel@386:         listener.Stop();
moel@386:         listenerThread = null;
moel@386:       } catch (HttpListenerException) {
moel@386:       } catch (ThreadAbortException) {
moel@386:       } catch (NullReferenceException) {
moel@386:       } catch (Exception) {
moel@386:       }
moel@386:       return true;
moel@386:     }
moel@386: 
moel@387:     private void HandleRequests() {
moel@386: 
moel@386:       while (listener.IsListening) {
moel@386:         var context = listener.BeginGetContext(
moel@386:           new AsyncCallback(ListenerCallback), listener);
moel@386:         context.AsyncWaitHandle.WaitOne();
moel@386:       }
moel@386:     }
moel@386: 
moel@387:     private void ListenerCallback(IAsyncResult result) {
moel@386:       HttpListener listener = (HttpListener)result.AsyncState;
moel@386:       if (listener == null || !listener.IsListening)
moel@386:         return;
moel@387: 
moel@386:       // Call EndGetContext to complete the asynchronous operation.
moel@387:       HttpListenerContext context;
moel@387:       try {
moel@387:         context = listener.EndGetContext(result);     
moel@387:       } catch (Exception) {
moel@387:         return;
moel@387:       }
moel@387: 
moel@386:       HttpListenerRequest request = context.Request;
moel@386: 
moel@386:       var requestedFile = request.RawUrl.Substring(1);
moel@386:       if (requestedFile == "data.json") {
moel@387:         SendJSON(context.Response);
moel@386:         return;
moel@386:       }
moel@386: 
moel@386:       if (requestedFile.Contains("images_icon")) {
moel@387:         ServeResourceImage(context.Response, 
moel@387:           requestedFile.Replace("images_icon/", ""));
moel@386:         return;
moel@386:       }
moel@386: 
moel@386:       // default file to be served
moel@386:       if (string.IsNullOrEmpty(requestedFile))
moel@386:         requestedFile = "index.html";
moel@386: 
moel@386:       string[] splits = requestedFile.Split('.');
moel@386:       string ext = splits[splits.Length - 1];
moel@387:       ServeResourceFile(context.Response, 
moel@387:         "Web." + requestedFile.Replace('/', '.'), ext);
moel@386:     }
moel@386: 
moel@387:     private void ServeResourceFile(HttpListenerResponse response, string name, 
moel@386:       string ext) 
moel@348:     {
moel@386:       // resource names do not support the hyphen
moel@386:       name = "OpenHardwareMonitor.Resources." + 
moel@386:         name.Replace("custom-theme", "custom_theme");
moel@386: 
moel@386:       string[] names =
moel@386:         Assembly.GetExecutingAssembly().GetManifestResourceNames();
moel@386:       for (int i = 0; i < names.Length; i++) {
moel@386:         if (names[i].Replace('\\', '.') == name) {
moel@386:           using (Stream stream = Assembly.GetExecutingAssembly().
moel@386:             GetManifestResourceStream(names[i])) {
moel@387:             response.ContentType = GetcontentType("." + ext);
moel@387:             response.ContentLength64 = stream.Length;
moel@386:             byte[] buffer = new byte[512 * 1024];
moel@386:             int len;
moel@387:             try {
moel@387:               Stream output = response.OutputStream;
moel@387:               while ((len = stream.Read(buffer, 0, buffer.Length)) > 0) {
moel@387:                 output.Write(buffer, 0, len);
moel@387:               }
moel@387:               output.Close();
moel@387:             } catch (HttpListenerException) {
moel@386:             }
moel@387:             response.Close();
moel@387:             return;
moel@387:           }          
moel@348:         }
moel@386:       }
moel@387: 
moel@387:       response.StatusCode = 404;
moel@387:       response.Close();
moel@386:     }
moel@348: 
moel@387:     private void ServeResourceImage(HttpListenerResponse response, string name) {
moel@386:       name = "OpenHardwareMonitor.Resources." + name;
moel@348: 
moel@386:       string[] names =
moel@386:         Assembly.GetExecutingAssembly().GetManifestResourceNames();
moel@386:       for (int i = 0; i < names.Length; i++) {
moel@386:         if (names[i].Replace('\\', '.') == name) {
moel@386:           using (Stream stream = Assembly.GetExecutingAssembly().
moel@386:             GetManifestResourceStream(names[i])) {
moel@348: 
moel@386:             Image image = Image.FromStream(stream);
moel@387:             response.ContentType = "image/png";
moel@387:             try {
moel@387:               Stream output = response.OutputStream;
moel@387:               using (MemoryStream ms = new MemoryStream()) {
moel@387:                 image.Save(ms, ImageFormat.Png);
moel@387:                 ms.WriteTo(output);
moel@387:               }
moel@387:               output.Close();
moel@387:             } catch (HttpListenerException) {              
moel@348:             }
moel@386:             image.Dispose();
moel@387:             response.Close();
moel@386:             return;
moel@386:           }
moel@386:         }
moel@386:       }
moel@387: 
moel@387:       response.StatusCode = 404;
moel@387:       response.Close();
moel@386:     }
moel@348: 
moel@387:     private void SendJSON(HttpListenerResponse response) {
moel@348: 
moel@386:       string JSON = "{\"id\": 0, \"Text\": \"Sensor\", \"Children\": [";
moel@386:       nodeCount = 1;
moel@386:       JSON += GenerateJSON(root);
moel@386:       JSON += "]";
moel@386:       JSON += ", \"Min\": \"Min\"";
moel@386:       JSON += ", \"Value\": \"Value\"";
moel@386:       JSON += ", \"Max\": \"Max\"";
moel@386:       JSON += ", \"ImageURL\": \"\"";
moel@386:       JSON += "}";
moel@348: 
moel@386:       var responseContent = JSON;
moel@386:       byte[] buffer = Encoding.UTF8.GetBytes(responseContent);
moel@348: 
moel@387:       response.AddHeader("Cache-Control", "no-cache");
moel@348: 
moel@387:       response.ContentLength64 = buffer.Length;
moel@387:       response.ContentType = "application/json";
moel@348: 
moel@387:       try {
moel@387:         Stream output = response.OutputStream;
moel@387:         output.Write(buffer, 0, buffer.Length);
moel@387:         output.Close();
moel@387:       } catch (HttpListenerException) {
moel@387:       }
moel@387: 
moel@387:       response.Close();
moel@386:     }
moel@348: 
moel@386:     private string GenerateJSON(Node n) {
moel@386:       string JSON = "{\"id\": " + nodeCount + ", \"Text\": \"" + n.Text 
moel@386:         + "\", \"Children\": [";
moel@386:       nodeCount++;
moel@348: 
moel@386:       foreach (Node child in n.Nodes)
moel@386:         JSON += GenerateJSON(child) + ", ";
moel@386:       if (JSON.EndsWith(", "))
moel@386:         JSON = JSON.Remove(JSON.LastIndexOf(","));
moel@386:       JSON += "]";
moel@348: 
moel@386:       if (n is SensorNode) {
moel@386:         JSON += ", \"Min\": \"" + ((SensorNode)n).Min + "\"";
moel@386:         JSON += ", \"Value\": \"" + ((SensorNode)n).Value + "\"";
moel@386:         JSON += ", \"Max\": \"" + ((SensorNode)n).Max + "\"";
moel@386:         JSON += ", \"ImageURL\": \"images/transparent.png\"";
moel@386:       } else if (n is HardwareNode) {
moel@386:         JSON += ", \"Min\": \"\"";
moel@386:         JSON += ", \"Value\": \"\"";
moel@386:         JSON += ", \"Max\": \"\"";
moel@386:         JSON += ", \"ImageURL\": \"images_icon/" + 
moel@386:           GetHardwareImageFile((HardwareNode)n) + "\"";
moel@386:       } else if (n is TypeNode) {
moel@386:         JSON += ", \"Min\": \"\"";
moel@386:         JSON += ", \"Value\": \"\"";
moel@386:         JSON += ", \"Max\": \"\"";
moel@386:         JSON += ", \"ImageURL\": \"images_icon/" + 
moel@386:           GetTypeImageFile((TypeNode)n) + "\"";
moel@386:       } else {
moel@386:         JSON += ", \"Min\": \"\"";
moel@386:         JSON += ", \"Value\": \"\"";
moel@386:         JSON += ", \"Max\": \"\"";
moel@386:         JSON += ", \"ImageURL\": \"images_icon/computer.png\"";
moel@386:       }
moel@348: 
moel@386:       JSON += "}";
moel@386:       return JSON;
moel@386:     }
moel@348: 
moel@386:     private static void ReturnFile(HttpListenerContext context, string filePath) 
moel@386:     {
moel@386:       context.Response.ContentType = 
moel@386:         GetcontentType(Path.GetExtension(filePath));
moel@386:       const int bufferSize = 1024 * 512; //512KB
moel@386:       var buffer = new byte[bufferSize];
moel@386:       using (var fs = File.OpenRead(filePath)) {
moel@348: 
moel@386:         context.Response.ContentLength64 = fs.Length;
moel@386:         int read;
moel@386:         while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
moel@386:           context.Response.OutputStream.Write(buffer, 0, read);
moel@386:       }
moel@348: 
moel@386:       context.Response.OutputStream.Close();
moel@386:     }
moel@348: 
moel@386:     private static string GetcontentType(string extension) {
moel@386:       switch (extension) {
moel@386:         case ".avi": return "video/x-msvideo";
moel@386:         case ".css": return "text/css";
moel@386:         case ".doc": return "application/msword";
moel@386:         case ".gif": return "image/gif";
moel@386:         case ".htm":
moel@386:         case ".html": return "text/html";
moel@386:         case ".jpg":
moel@386:         case ".jpeg": return "image/jpeg";
moel@386:         case ".js": return "application/x-javascript";
moel@386:         case ".mp3": return "audio/mpeg";
moel@386:         case ".png": return "image/png";
moel@386:         case ".pdf": return "application/pdf";
moel@386:         case ".ppt": return "application/vnd.ms-powerpoint";
moel@386:         case ".zip": return "application/zip";
moel@386:         case ".txt": return "text/plain";
moel@386:         default: return "application/octet-stream";
moel@386:       }
moel@386:     }
moel@348: 
moel@386:     private static string GetHardwareImageFile(HardwareNode hn) {
moel@348: 
moel@386:       switch (hn.Hardware.HardwareType) {
moel@386:         case HardwareType.CPU:
moel@386:           return "cpu.png";
moel@386:         case HardwareType.GpuNvidia:
moel@386:           return "nvidia.png";
moel@386:         case HardwareType.GpuAti:
moel@386:           return "ati.png";
moel@386:         case HardwareType.HDD:
moel@386:           return "hdd.png";
moel@386:         case HardwareType.Heatmaster:
moel@386:           return "bigng.png";
moel@386:         case HardwareType.Mainboard:
moel@386:           return "mainboard.png";
moel@386:         case HardwareType.SuperIO:
moel@386:           return "chip.png";
moel@386:         case HardwareType.TBalancer:
moel@386:           return "bigng.png";
moel@386:         case HardwareType.RAM:
moel@386:           return "ram.png";
moel@386:         default:
moel@386:           return "cpu.png";
moel@386:       }
moel@348: 
moel@386:     }
moel@348: 
moel@386:     private static string GetTypeImageFile(TypeNode tn) {
moel@348: 
moel@386:       switch (tn.SensorType) {
moel@386:         case SensorType.Voltage:
moel@386:           return "voltage.png";
moel@386:         case SensorType.Clock:
moel@386:           return "clock.png";
moel@386:         case SensorType.Load:
moel@386:           return "load.png";
moel@386:         case SensorType.Temperature:
moel@386:           return "temperature.png";
moel@386:         case SensorType.Fan:
moel@386:           return "fan.png";
moel@386:         case SensorType.Flow:
moel@386:           return "flow.png";
moel@386:         case SensorType.Control:
moel@386:           return "control.png";
moel@386:         case SensorType.Level:
moel@386:           return "level.png";
moel@386:         case SensorType.Power:
moel@386:           return "power.png";
moel@386:         default:
moel@386:           return "power.png";
moel@386:       }
moel@348: 
moel@386:     }
moel@348: 
moel@386:     public int ListenerPort {
moel@386:       get { return listenerPort; }
moel@386:       set { listenerPort = value; }
moel@386:     }
moel@348: 
moel@386:     ~HttpServer() {
moel@387:       if (PlatformNotSupported)
moel@387:         return;
moel@387: 
moel@386:       StopHTTPListener();
moel@386:       listener.Abort();
moel@386:     }
moel@348: 
moel@386:     public void Quit() {
moel@387:       if (PlatformNotSupported)
moel@387:         return;
moel@387: 
moel@386:       StopHTTPListener();
moel@386:       listener.Abort();
moel@348:     }
moel@386:   }
moel@348: }