3
\$\begingroup\$

Attached is some code I have written for my application. It is an OWIN based web server which connects an Alexa Custom Skill, my security camera feed, and Microsoft Azure Face API.

The number one thing to remember when creating an Alexa custom skill is that the bot times-out after about ten seconds. Because my service is collecting Security Camera Feeds for analysis from Microsoft Face API, it has to move fast. Really Fast!

I've been able to utilize Tasks, and other TPL methods to make the service quickly gather the results I've been looking for.

Originally, my SecurityCameraApi Class Library would try and collect proper data within each request to the service.

This involved making several requests to the Cameras on-board web server to narrow down each URL to sort out saved static images, and recorded video data.

This took time. Time Alexa is unforgiving about giving you time.

Using the TPL to send off multiple images to the Face API at once saved some time, however, sorting Data from the Security Camera is still a problem.

Note: With all the different Cameras on the market, there are so many ways they save data. One thing you can count on when building a generic way of reading data from Security Camera is that they will most likely save information in a DateTime format... of some kind.

I have finally decided that the fastest way to collect the camera data cannot happen when an Alexa Request is made.

Instead, I have opted to collect and organize data in a constant fashion using a Threading.Timer, and keeping the results in a prefabricated list of images (in byte[], which is how I need the information stored).

This means that an HTTP request for the camera's data is made more often (much more often).

I have attempted to keep my code clean (as I can) so other people are able to follow it.

Is it a good idea to poll data like that, and pre-process it?

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using SecurityConfiguration;

namespace SecurityCameraApi
{
// ReSharper disable InconsistentNaming

public class SecurityCamera  //, IDisposable
{
    private static Timer ImageCollectionUpdater { get; set; }
    private static Link CurrentDateDirectoryLink { get; set; }
    private static SecurityCamera camera { get; set; }

    private static List<byte[]> savedImageCollection;
    public List<byte[]> SaveImageCollection
    {
        get { return savedImageCollection; }
        private set { savedImageCollection = value; }
    }

    public SecurityCamera()
    {
        camera = this;
    }

    public void StartImageCollection()
    {
        ImageCollectionUpdater = new Timer(UpdateImageCollectionData);
        ImageCollectionUpdater.Change(0, 500);
    }



    private static readonly ConfigurationDto config = Configuration.GetSettings();

    //public static Func<byte[], string> ConvertByteToBase64 = b => Convert.ToBase64String(b);

    private static List<byte[]> GetListOfSavedImagesAsByte()
    {
        var list = new List<byte[]>();

        Func<List<Link>, List<Link>> lastTwelveListEntities = e =>
                                             e.Reverse<Link>().Take(15).Reverse().Take(12).ToList();
        try
        {
            string dateDirectoryData = GetUrlData(config.SecurityCameraSdUrl + CurrentDateDirectoryLink.Text);

            string currentImagesDirectoryHref = "";

            Link currentImagesDirectoryLink;
            if (IsCurrentImageLinkDirectory(dateDirectoryData, out currentImagesDirectoryLink))
                currentImagesDirectoryHref = currentImagesDirectoryLink.Href;

            List<Link> links = ParseLinkData(
                GetUrlData(config.SecurityCameraUrl + currentImagesDirectoryHref)).ToList();

            Parallel.ForEach(links.Count() >= 10 ? lastTwelveListEntities(links) : links, link => 
                list.Add(camera.GetImageAsByte(config.SecurityCameraUrl + currentImagesDirectoryHref + link.Text)));
        }
        catch
        {
        }

        list.RemoveAll(item => item == (null));

        return list;
    }

    public byte[] GetImageAsByte(string url)
    {
        try
        {
            using (var wc = new WebClient
            {
                Credentials = new NetworkCredential(
                    config.SecurityCameraCredentialsUserName,
                    config.SecurityCameraCredentialsPassword
                    )
            })
            {
                byte[] imgByteData = wc.DownloadData(url);

                return imgByteData;
            }
        }
        catch 
        {

        }
        return null;
    }

    //----------------------------------------------------------------//

    private struct Link
    {
        public string Href { get; set; }
        public string Text { get; set; }
    }

    private static IEnumerable<Link> ParseLinkData(string linkData)
    {
        var list = new List<Link>();

        IEnumerable matches = Regex.Matches(linkData, @"(<a.*?>.*?</a>)",
            RegexOptions.Singleline);

        Parallel.ForEach(matches.Cast<Match>(), match =>
        {
            string value = match.Groups[1].Value;

            var link = new Link();

            Match href = Regex.Match(value, @"href=\""(.*?)\""",
                RegexOptions.Singleline);

            if (href.Success) link.Href = href.Groups[1].Value;

            link.Text = Regex.Replace(value, @"\s*<.*?>\s*", "", RegexOptions.Singleline);
            try
            {
                list.Add(link);
            }
            catch
            {
            }
        });
        return list;
    }

    private static Int64 FindOnlyNumericCharatersInString(string T)
    {
        try
        {
            return Int64.Parse(Regex.Replace(T, "[^0-9.]", String.Empty).Replace(".", String.Empty));
        }
        catch
        {
        }
        return 0;
    }

    private static string GetUrlData(string sdCardDataUrl)
    {
        try
        {
            // Log into the security camera
            using (var wc = new WebClient
            {
                Credentials = new NetworkCredential(
                    config.SecurityCameraCredentialsUserName,
                    config.SecurityCameraCredentialsPassword
                    )
            })
            {
                return wc.DownloadString(sdCardDataUrl);
            }
        }
        catch
        {
        }
        return null;
    }

    private static Link GetCurrentDateDirectoryLink()
    {
        Func<DateTime, bool> isCurrentDate =
            dt =>
                dt.Year.Equals(DateTime.Now.Year) && dt.Month.Equals(DateTime.Now.Month) &&
                dt.Day.Equals(DateTime.Now.Day);

        string rootDirectoryData = GetUrlData(config.SecurityCameraSdUrl);
        var l = new Link();
        try
        {
            Parallel.ForEach(ParseLinkData(rootDirectoryData), link =>
            {

                DateTime linkDate = DateSerializer.ParseFormatFromString(
                    FindOnlyNumericCharatersInString(link.Text).ToString(CultureInfo.InvariantCulture));

                if (isCurrentDate(linkDate))
                {
                    l = link;
                }
            });
        }
        catch
        {
        }
        return l;
    }

    //----------------------------------------------------------------//

    private static void UpdateImageCollectionData(object args)
    {
        ImageCollectionUpdater.Change(Timeout.Infinite, Timeout.Infinite);
        CurrentDateDirectoryLink = GetCurrentDateDirectoryLink();
        camera.SaveImageCollection = GetListOfSavedImagesAsByte();
        ImageCollectionUpdater.Change(400, 500);
    }


    private static bool IsCurrentImageLinkDirectory(string urlData, out 
Link currentImagesLinkDirectory)
    {
        //There has to  be a better way of splitting a string and sending 
back the best option!
        var dict = new Dictionary<int, Link>();
        Parallel.ForEach(ParseLinkData(urlData), link =>
        {
            int i = Int32.MaxValue;
            try
            {
                i = 
Convert.ToInt32(FindOnlyNumericCharatersInString(link.Text));
            }
            catch
            {
            }
            if (!i.Equals(Int32.MaxValue) && link.Text.Contains("image"))
            {
                dict.Add(i, link);
            }
        });
        //var dict = ParseLinkData(urlData).ToDictionary(link => 
Convert.ToInt32(FindOnlyNumericCharatersInString(link.Text)));
        currentImagesLinkDirectory = dict[dict.Keys.Max()];
        dict.Clear();
        return true;
    }

    //--------------------------------------------------------------//

   }
}

I haven't quite finished refactoring some of the methods. I realize that there are areas where I could use Generics to tighten up the code.

\$\endgroup\$
  • \$\begingroup\$ What happened to the IDisposable interface? Could you clean this up a little bit? The dead code here and there is a little bit confusing. \$\endgroup\$ – t3chb0t Aug 14 '18 at 20:16
  • \$\begingroup\$ Yes, I will reimplement the interface. \$\endgroup\$ – user2224583 Aug 14 '18 at 20:42
2
\$\begingroup\$

If the data source can't give you new data fast enough to allow you to meet your time constraints, then you have to be able to run without calling the data source. In your case, that means having camera data already available, and that's going to require a solution similar to the one you describe. There are a few things to consider.

  1. You say that some cameras are fast enough. Can you determine whether the one you have is, and if so load on demand?
  2. Is it possible for you to know when your API is under use, and pull data more frequently when it is, and less frequently when it's not? Or is there really any downside to pulling data as fast as you can?
  3. Is it worth trying to load the data from the data source as a task with a timeout, so you can return that if you get it fast enough?

As for the code itself, there's a lot that could be improved. The class does everything: downloads data, parses HTML, parses strings. It's at least 3 different classes in one. It doesn't do any error handling - there are a lot of empty catch blocks. There's use of non-threadsafe objects in a threaded environment (e.g. list.Add()) - which is presumably why you added the try block there.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.