/// <summary>
        /// Create applications and grant user permissions
        /// </summary>
        /// <param name="nameSpace"></param>
        /// <param name="name"></param>
        /// <param name="organization"></param>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        private static App CreateApplication(PackageConfigFile config, Organization organization, string sessionId) {

            App app = new App();
            app.ID = string.Format("{0:X8}", (organization.Name + config.Namespace).GetHashCode());
            app.Namespace = config.Namespace;

            app.Name = config.DisplayName;
            app.UrlName = AssureUniqueUrlName(organization, app.Name);
            app.EntryPoint = "/" + config.AppName;
            app.Description = config.Description;

            // Connect software with an organization
            app.Organization = organization;

            // Apply permissions
            SystemUserSession userSession = Db.SQL<SystemUserSession>("SELECT o FROM Simplified.Ring5.SystemUserSession o WHERE o.SessionIdString = ?", sessionId).First;
            if (userSession != null && userSession.Token != null) {
                new SoftwarePermission() { User = userSession.Token.User, Software = app };
            }
            else {
                throw new InvalidOperationException("Failed to retrive user session");
            }

            return app;
        }
        /// <summary>
        /// unpack image from package
        /// TODO: Assure random filename is unique
        /// </summary>
        /// <param name="packageZip"></param>
        /// <param name="config"></param>
        /// <param name="destination"></param>
        /// <param name="url"></param>
        private static void UnpackAppStoreMetadata(Software software, string packageZip, PackageConfigFile config, string destination) {

            string fileName = Path.GetRandomFileName();
            if (!Directory.Exists(destination)) {
                Directory.CreateDirectory(destination);
            }
            //string destinationFileName = Path.Combine(destination, fileName);
            //destinationFileName = Path.ChangeExtension(destinationFileName, Path.GetExtension(config.ImageUri));

            Dictionary<string, string> icons = new Dictionary<string, string>();
            Dictionary<string, string> banners = new Dictionary<string, string>();
            Dictionary<string, string> screenshots = new Dictionary<string, string>();
            Dictionary<string, string> appstoreicons = new Dictionary<string, string>();


            string metadataFolder = "metadata/";
            string bannerMap = "banner";
            string screenshotMap = "screenshot";
            string appiconMap = "appicon";

            using (ZipArchive archive = ZipFile.OpenRead(packageZip)) {

                #region Find Media
                foreach (ZipArchiveEntry entry in archive.Entries) {

                    if (entry.FullName.Equals(config.ImageUri, StringComparison.InvariantCultureIgnoreCase)) {
                        // Icon
                        string uniqueFileName;
                        string extention = Path.GetExtension(entry.FullName);
                        Utils.GetUniqueFilename(destination, extention, out uniqueFileName);
                        icons.Add(entry.FullName, uniqueFileName);
                    }

                    if (entry.FullName.StartsWith(metadataFolder + bannerMap, StringComparison.InvariantCultureIgnoreCase)) {
                        // Banner
                        string uniqueFileName;
                        string extention = Path.GetExtension(entry.FullName);
                        Utils.GetUniqueFilename(destination, extention, out uniqueFileName);
                        banners.Add(entry.FullName, uniqueFileName);
                    }

                    if (entry.FullName.StartsWith(metadataFolder + screenshotMap, StringComparison.InvariantCultureIgnoreCase)) {
                        // Screenshot
                        string uniqueFileName;
                        string extention = Path.GetExtension(entry.FullName);
                        Utils.GetUniqueFilename(destination, extention, out uniqueFileName);
                        screenshots.Add(entry.FullName, uniqueFileName);
                    }

                    if (entry.FullName.StartsWith(metadataFolder + appiconMap, StringComparison.InvariantCultureIgnoreCase)) {

                        // Appstoreicon
                        string uniqueFileName;
                        string extention = Path.GetExtension(entry.FullName);
                        Utils.GetUniqueFilename(destination, extention, out uniqueFileName);
                        appstoreicons.Add(entry.FullName, uniqueFileName);
                    }

                }
                #endregion

                #region Unpack
                foreach (var item in icons) {

                    PackageManager.UnpackFile(archive, item.Key, item.Value);

                    string hash = Utils.CalculateHash(item.Value);

                    // Dup check
                    SoftwareIcon softwareIcon = Db.SQL<SoftwareIcon>("SELECT o FROM Warehouse.SoftwareIcon o WHERE o.Software=? AND o.Hash=?", software, hash).First;
                    if (softwareIcon != null) {
                        // Delete new file
                        File.Delete(item.Value);
                    }
                    else {
                        SoftwareIcon img = new SoftwareIcon();
                        img.Software = software;
                        img.File = item.Value;
                        img.Position = 0;
                        img.Hash = hash;
                    }
                }

                foreach (var item in banners) {

                    PackageManager.UnpackFile(archive, item.Key, item.Value);

                    string hash = Utils.CalculateHash(item.Value);
                    SoftwareBanner img = Db.SQL<SoftwareBanner>("SELECT o FROM Warehouse.SoftwareBanner o WHERE o.Software=? AND o.Hash=?", software, hash).First;
                    if (img != null) {
                        // Delete new file
                        File.Delete(item.Value);
                    }
                    else {
                        img = new SoftwareBanner();
                        img.Software = software;
                        img.File = item.Value;
                        img.Hash = hash;
                    }

                    try {
                        string fName = Path.GetFileNameWithoutExtension(item.Key).Substring(bannerMap.Length);
                        fName = fName.Trim(new Char[] { ' ', '-', '_' });
                        img.Position = int.Parse(fName);
                    }
                    catch (Exception) { }
                }

                foreach (var item in screenshots) {

                    PackageManager.UnpackFile(archive, item.Key, item.Value);
                    string hash = Utils.CalculateHash(item.Value);
                    SoftwareScreenshot img = Db.SQL<SoftwareScreenshot>("SELECT o FROM Warehouse.SoftwareScreenshot o WHERE o.Software=? AND o.Hash=?", software, hash).First;
                    if (img != null) {
                        // Delete new file
                        File.Delete(item.Value);
                    }
                    else {
                        img = new SoftwareScreenshot();
                        img.Software = software;
                        img.File = item.Value;
                        img.Hash = hash;
                    }

                    try {
                        string fName = Path.GetFileNameWithoutExtension(item.Key).Substring(screenshotMap.Length);
                        fName = fName.Trim(new Char[] { ' ', '-', '_' });
                        img.Position = int.Parse(fName);
                    }
                    catch (Exception) { }
                }

                foreach (var item in appstoreicons) {

                    PackageManager.UnpackFile(archive, item.Key, item.Value);
                    string hash = Utils.CalculateHash(item.Value);

                    SoftwareIcon img = Db.SQL<SoftwareIcon>("SELECT o FROM Warehouse.SoftwareIcon o WHERE o.Software=? AND o.Hash=?", software, hash).First;
                    if (img != null) {
                        // Delete new file
                        File.Delete(item.Value);
                    }
                    else {
                        img = new SoftwareIcon();
                        img.Software = software;
                        img.File = item.Value;
                        img.Hash = hash;
                    }

                    try {
                        string fName = Path.GetFileNameWithoutExtension(item.Key).Substring(appiconMap.Length);
                        fName = fName.Trim(new Char[] { ' ', '-', '_' });
                        img.Position = int.Parse(fName);
                    }
                    catch (Exception) { }
                }
                #endregion
            }
        }
        /// <summary>
        /// Validate app configuration
        /// </summary>
        /// <param name="config"></param>
        private static void ValidateConfiguration(ZipArchive archive, PackageConfigFile config) {

            if (string.IsNullOrEmpty(config.Namespace)) {
                throw new InvalidOperationException("Invalid Namespace <tag> in package configuration");
            }

            // TODO: Validate Namespace for invalid characters (this will be uses for folder naming)

            if (string.IsNullOrEmpty(config.DisplayName)) {
                throw new InvalidOperationException("Invalid DisplayName <tag> in package configuration");
            }

            if (string.IsNullOrEmpty(config.Channel)) {
                throw new InvalidOperationException("Invalid Channel <tag> in package configuration");
            }

            // TODO: Validate Channel for invalid characters (this will be uses for folder naming)


            if (string.IsNullOrEmpty(config.Version)) {
                throw new InvalidOperationException("Invalid Version <tag> in package configuration");
            }

            //try {
            //    new Version(config.Version);
            //}
            //catch (Exception) {
            //    throw new InvalidOperationException("Invalid Version <tag> in package configuration");
            //}

            if (string.IsNullOrEmpty(config.Executable)) {
                throw new InvalidOperationException("Invalid Executable <tag> in package configuration");
            }

            try {
                VerifyPackageEntry(archive, config.Executable);
            }
            catch (Exception e) {
                throw new InvalidOperationException(e.Message + ", " + config.Executable);
            }

            // Resource folder
            // Note: Resource tag can be empty
            if (!string.IsNullOrEmpty(config.ResourceFolder)) {

                // Assure that the path is a folder path (ending with an "/" )
                if (!config.ResourceFolder.EndsWith("/")) {
                    config.ResourceFolder += "/";
                }

                try {
                    VerifyPackageEntry(archive, config.ResourceFolder);
                }
                catch (Exception e) {
                    throw new InvalidOperationException(e.Message + ", " + config.ResourceFolder);
                }

            }

            if (!string.IsNullOrEmpty(config.ImageUri)) {
                try {
                    VerifyPackageEntry(archive, config.ImageUri);
                }
                catch (Exception e) {
                    throw new InvalidOperationException(e.Message + ", " + config.ImageUri);
                }
            }

            // TODO: Add more validations
        }
        /// <summary>
        /// Get App configuration from zipped package
        /// </summary>
        /// <param name="packageZip"></param>
        /// <param name="config"></param>
        public static void ReadConfiguration(string packageZip, out PackageConfigFile config) {
            lock (PackageManager.locker) {

                using (ZipArchive archive = ZipFile.OpenRead(packageZip)) {
                    ReadConfiguration(archive, out config);
                }
            }
        }
        /// <summary>
        /// Get App configuration from zipped package
        /// </summary>
        /// <param name="packageZip"></param>
        /// <param name="config"></param>
        public static void ReadConfiguration(ZipArchive archive, out PackageConfigFile config) {
            lock (PackageManager.locker) {

                try {

                    foreach (ZipArchiveEntry entry in archive.Entries) {
                        if (entry.FullName.Equals(PackageManager.packageConfigurationFileName, StringComparison.OrdinalIgnoreCase)) {

                            Stream s = entry.Open();
                            XmlSerializer ser = new XmlSerializer(typeof(PackageConfigFile));
                            config = ser.Deserialize(s) as PackageConfigFile;
                            return;
                        }
                    }
                }
                catch (InvalidOperationException e) {
                    throw new InvalidDataException("Invalid package format", e);
                }
                catch (Exception) {
                    throw;
                }

                throw new FileNotFoundException(string.Format("Missing package configuration file ({0})", PackageManager.packageConfigurationFileName));
            }
        }
        /// <summary>
        /// Create App configuration from package configuration
        /// </summary>
        /// <param name="packageConfig"></param>
        /// <param name="config"></param>
        private static void CreateConfig(PackageConfigFile packageConfig, string sourceUrl, string storeUrl, string imageUri, out DeployedConfigFile config) {

            // Create app configuration file
            config = new DeployedConfigFile();
            config.Namespace = packageConfig.Namespace;
            config.Channel = packageConfig.Channel;
            config.Version = packageConfig.Version;

            config.AppName = packageConfig.AppName;

            config.Executable = packageConfig.Executable;
            config.ResourceFolder = packageConfig.ResourceFolder;

            config.DisplayName = packageConfig.DisplayName;
            config.Company = packageConfig.Company;
            config.Description = packageConfig.Description;
            config.Heading = packageConfig.Heading;
            config.VersionDate = packageConfig.VersionDate;

            Uri u = new Uri(sourceUrl);
            if (u.IsFile) {
                config.SourceID = string.Format("{0:X8}", u.LocalPath.GetHashCode());
            }
            else {
                config.SourceID = u.Segments[u.Segments.Length - 1];
            }
            config.SourceUrl = sourceUrl;

            Uri su = new Uri(storeUrl);
            if (su.IsFile) {
                config.StoreID = string.Format("{0:X8}", su.LocalPath.GetHashCode());
            }
            else {
                config.StoreID = su.Segments[su.Segments.Length - 1];
            }
            config.StoreUrl = storeUrl;

            config.ImageUri = imageUri;
            config.CanBeUninstalled = true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="archive"></param>
        /// <param name="config"></param>
        /// <param name="imageResourceFolder"></param>
        /// <param name="imageUri"></param>
        private static void UnpackAppImage(ZipArchive archive, PackageConfigFile config, string imageResourceFolder, out string imageUri) {

            string createdDestinationFolder = Utils.CreateDirectory(imageResourceFolder);

            try {
                // Unpack app image to starcounter admin folder
                ZipArchiveEntry entry = archive.GetEntry(config.ImageUri);
                if (entry != null) {

                    string imageFileName = Path.ChangeExtension(Path.GetRandomFileName(), Path.GetExtension(config.ImageUri));
                    string imageFile = Path.Combine(imageResourceFolder, imageFileName);
                    entry.ExtractToFile(imageFile, true);

                    string imageFolder = Path.GetFileName(imageResourceFolder);
                    imageUri = string.Format("{0}", imageFileName);
                }
                else {
                    // TODO: Use default image?
                    imageUri = config.ImageUri;
                }
            }
            catch (Exception) {

                if (createdDestinationFolder != null) {
                    Directory.Delete(createdDestinationFolder, true);
                }
                throw;
            }
        }