Compare commits

..

9 Commits

996 changed files with 16711 additions and 45334 deletions
-4
View File
@@ -56,7 +56,6 @@ build/*.nupkg
src/Umbraco.Tests/config/applications.config
src/Umbraco.Tests/config/trees.config
src/Umbraco.Web.UI/web.config
src/Umbraco.Web.UI/Config/ClientDependency.config
*.orig
src/Umbraco.Tests/config/404handlers.config
src/Umbraco.Web.UI/[Vv]iews/*.cshtml
@@ -140,6 +139,3 @@ apidocs/api/*
build/docs.zip
build/ui-docs.zip
build/csharp-docs.zip
build/msbuild.log
.vs/
src/packages/
+13 -25
View File
@@ -1,52 +1,40 @@
Umbraco CMS
===========
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
[![ScreenShot](vimeo.png)](https://vimeo.com/172382998/)
## Umbraco CMS ##
Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
Umbraco is a free open source Content Management System built on the ASP.NET platform.
## Building Umbraco from source ##
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`.
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt dev` in `src\Umbraco.Web.UI.Client`.
If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md).
If you're interested in making changes to Belle make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md). Note that you can always [download a nightly build](http://nightly.umbraco.org/umbraco%207.0.0/) so you don't have to build the code yourself.
Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself.
## Watch an introduction video ##
## Watch a introduction video ##
[![ScreenShot](http://umbraco.com/images/whatisumbraco.png)](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/)
## Umbraco - The Friendly CMS ##
## Umbraco - the simple, flexible and friendly ASP.NET CMS ##
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box.
**More than 350,000 sites trust Umbraco**
Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live.
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developer's dream and your users will love it too.
Used by more than 350,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 200,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
Used by more than 350,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales.
To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies)
## Why Open Source? ##
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use.
To view more examples, please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies)
## Downloading ##
The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download).
The downloadable Umbraco releases live at [http://our.umbraco.org/download](http://our.umbraco.org/download).
## Forums ##
Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org).
We have a forum running on [http://our.umbraco.org](http://our.umbraco.org). The discussions group on [Google Groups](https://groups.google.com/forum/#!forum/umbraco-dev) is for discussions on developing the core, and not on Umbraco-implementations or extensions in general. For those topics, please use [http://our.umbraco.org](http://our.umbraco.org).
## Contribute to Umbraco ##
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute).
If you want to contribute back to Umbraco you should check out our [guide to contributing](http://our.umbraco.org/contribute).
## Found a bug? ##
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](http://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
+26 -33
View File
@@ -2,52 +2,45 @@ version: '{build}'
shallow_clone: true
build_script:
- cmd: >-
SET SLN=%CD%
SET SRC=%SLN%\src
SET PACKAGES=%SRC%\packages
CD build
cd build
SET "release="
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i"
ECHO "Restoring NuGet into %PACKAGES%"
%SRC%\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
%SRC%\.nuget\NuGet.exe restore %SRC%\umbraco.sln -Verbosity Quiet -NonInteractive -PackagesDirectory %PACKAGES%
SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH%
ECHO %PATH%
ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER%
SET PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH%
SET nuGetFolder=%CD%\..\src\packages\
..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
IF EXIST ..\src\umbraco.businesslogic\packages.config ..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe"
XCOPY "%SRC%\Umbraco.Tests\unit-test-log4net.CI.config" "%SRC%\Umbraco.Tests\unit-test-log4net.config" /Y
%MSBUILD% "%SLN%/src/Umbraco.Tests/Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /p:NugetPackagesDirectory=%PACKAGES%
build.bat -integration -release:%release% -comment:build%APPVEYOR_BUILD_NUMBER% -nugetfolder:%PACKAGES%
%MSBUILD% "../src/Umbraco.Tests/Umbraco.Tests.csproj" /verbosity:minimal
build.bat %release% build%APPVEYOR_BUILD_NUMBER%
ECHO %PATH%
test:
assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll
artifacts:
- path: build\UmbracoCms.*
name: UmbracoFiles
- path: build\msbuild.log
name: BuildLog
deploy:
- provider: AzureBlob
storage_account_name: umbraconightlies
storage_access_key:
secure: bmEMml2SF7QLHULiePa/a01XOeIa2SxJeXuaZ+1R27b+Vb2nNUQVYiPlUyF2cZAFSHI/zO/LekRsVU1rTescGhJjF7SSjKybymI3p+F/OWpwqiu2WfFee1ofXBFx8QHw
container: umbraco-750
artifact: UmbracoFiles
on:
branch: dev-v7
notifications:
- provider: Slack
auth_token:
+26 -142
View File
@@ -1,174 +1,58 @@
@ECHO OFF
:: UMBRACO BUILD FILE
:: ensure we have UmbracoVersion.txt
IF NOT EXIST UmbracoVersion.txt (
ECHO UmbracoVersion.txt is missing!
GOTO error
ECHO UmbracoVersion.txt missing!
GOTO :showerror
)
REM Get the version and comment from UmbracoVersion.txt lines 2 and 3
SET RELEASE=
SET COMMENT=
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED RELEASE SET RELEASE=%%i
FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED COMMENT SET COMMENT=%%i
SET "release="
SET "comment="
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i"
FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED comment SET "comment=%%i"
REM process args
REM If there's arguments on the command line overrule UmbracoVersion.txt and use that as the version
IF [%1] NEQ [] (SET release=%1)
IF [%2] NEQ [] (SET comment=%2) ELSE (IF [%1] NEQ [] (SET "comment="))
SET INTEGRATION=0
SET nuGetFolder=%CD%\..\src\packages
SET SKIPNUGET=0
SET version=%release%
:processArgs
IF [%comment%] EQU [] (SET version=%release%) ELSE (SET version=%release%-%comment%)
ECHO Building Umbraco %version%
:: grab the first parameter as a whole eg "/action:start"
:: end if no more parameter
SET SWITCHPARSE=%1
IF [%SWITCHPARSE%] == [] goto endProcessArgs
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release%
:: get switch and value
SET SWITCH=
SET VALUE=
FOR /F "tokens=1,* delims=: " %%a IN ("%SWITCHPARSE%") DO SET SWITCH=%%a& SET VALUE=%%b
:: route arg
IF '%SWITCH%'=='/release' GOTO argRelease
IF '%SWITCH%'=='-release' GOTO argRelease
IF '%SWITCH%'=='/comment' GOTO argComment
IF '%SWITCH%'=='-comment' GOTO argComment
IF '%SWITCH%'=='/integration' GOTO argIntegration
IF '%SWITCH%'=='-integration' GOTO argIntegration
IF '%SWITCH%'=='/nugetfolder' GOTO argNugetFolder
IF '%SWITCH%'=='-nugetfolder' GOTO argNugetFolder
IF '%SWITCH%'=='/skipnuget' GOTO argSkipNuget
IF '%SWITCH%'=='-skipnuget' GOTO argSkipNuget
ECHO "Invalid switch %SWITCH%"
GOTO error
:: handle each arg
:argRelease
set RELEASE=%VALUE%
SHIFT
goto processArgs
:argComment
SET COMMENT=%VALUE%
SHIFT
GOTO processArgs
:argIntegration
SET INTEGRATION=1
SHIFT
GOTO processArgs
:argNugetFolder
SET nuGetFolder=%VALUE%
SHIFT
GOTO processArgs
:argSkipNuget
SET SKIPNUGET=1
SHIFT
GOTO processArgs
:endProcessArgs
REM run
SET VERSION=%RELEASE%
IF [%COMMENT%] EQU [] (SET VERSION=%RELEASE%) ELSE (SET VERSION=%RELEASE%-%COMMENT%)
ECHO ################################################################
ECHO Building Umbraco %VERSION%
ECHO ################################################################
SET MSBUILDPATH=C:\Program Files (x86)\MSBuild\14.0\Bin
SET MSBUILD="%MSBUILDPATH%\MsBuild.exe"
SET PATH="%MSBUILDPATH%";%PATH%
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %RELEASE%
ECHO.
ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle
RD ..\src\Umbraco.Web.UI.Client\build /Q /S
RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S
ECHO.
ECHO Removing existing built files to make sure everything is clean as a whistle
RMDIR /Q /S _BuildOutput
DEL /F /Q UmbracoCms.*.zip 2>NUL
DEL /F /Q UmbracoExamine.*.zip 2>NUL
DEL /F /Q UmbracoCms.*.nupkg 2>NUL
DEL /F /Q webpihash.txt 2>NUL
DEL /F /Q UmbracoCms.*.zip
DEL /F /Q UmbracoExamine.*.zip
DEL /F /Q UmbracoCms.*.nupkg
DEL /F /Q webpihash.txt
ECHO.
ECHO Making sure Git is in the path so that the build can succeed
CALL InstallGit.cmd
REM Adding the default Git path so that if it's installed it can actually be found
REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might find Git,
REM but the path setting is lost due to SETLOCAL
SET PATH="C:\Program Files (x86)\Git\cmd";"C:\Program Files\Git\cmd";%PATH%
ECHO.
ECHO Making sure we have a web.config
IF NOT EXIST "%CD%\..\src\Umbraco.Web.UI\web.config" COPY "%CD%\..\src\Umbraco.Web.UI\web.Template.config" "%CD%\..\src\Umbraco.Web.UI\web.config"
ECHO.
ECHO Reporting NuGet version
"%CD%\..\src\.nuget\NuGet.exe" help | findstr "^NuGet Version:"
ECHO.
ECHO Restoring NuGet packages
ECHO Into %nuGetFolder%
"%CD%\..\src\.nuget\NuGet.exe" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%"
IF ERRORLEVEL 1 GOTO :error
ECHO.
ECHO.
ECHO Performing MSBuild and producing Umbraco binaries zip files
ECHO This takes a few minutes and logging is set to report warnings
ECHO and errors only so it might seems like nothing is happening for a while.
ECHO You can check the msbuild.log file for progress.
ECHO.
%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory="%nuGetFolder%" /consoleloggerparameters:Summary;ErrorsOnly /fileLogger
IF ERRORLEVEL 1 GOTO error
%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /verbosity:minimal
ECHO.
ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project
attrib +h ..\src\Umbraco.Web.UI.Client\node_modules
IF %SKIPNUGET% EQU 1 GOTO success
ECHO.
ECHO Adding Web.config transform files to the NuGet package
REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform
REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform
ECHO.
ECHO Packing the NuGet release files
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %VERSION% -Symbols -Verbosity quiet
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %VERSION% -Verbosity quiet
IF ERRORLEVEL 1 GOTO error
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -Verbosity quiet
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% -Verbosity quiet
IF ERRORLEVEL 1 GOTO :showerror
:success
ECHO.
ECHO No errors were detected!
ECHO There may still be some in the output, which you would need to investigate.
ECHO Warnings are usually normal.
ECHO.
ECHO.
ECHO No errors were detected but you still may see some in the output, then it's time to investigate.
ECHO You might see some warnings but that is completely normal.
GOTO :EOF
:error
ECHO.
ECHO Errors were detected!
ECHO.
REM don't pause if continuous integration else the build server waits forever
REM before cancelling the build (and, there is noone to read the output anyways)
IF %INTEGRATION% NEQ 1 PAUSE
:showerror
PAUSE
+20 -18
View File
@@ -1,8 +1,8 @@
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
<!--
****************************************************
INCLUDES
INCLUDES
*****************************************************
-->
@@ -35,9 +35,9 @@
{
using (var cryptoProvider = new SHA1CryptoServiceProvider())
{
var fileHash = cryptoProvider.ComputeHash(stream);
using (TextWriter w = new StreamWriter(OutputFile, false))
{
w.WriteLine(string.Join("", fileHash.Select(b => b.ToString("x2"))));
@@ -52,9 +52,9 @@
</UsingTask>
<!--
<!--
****************************************************
VARIABLES
VARIABLES
*****************************************************
-->
@@ -102,9 +102,9 @@
<SystemFolders Include="$(WebAppFolder)Views" />
</ItemGroup>
<!--
<!--
****************************************************
TARGETS
TARGETS
*****************************************************
-->
@@ -155,7 +155,7 @@
<Message Text="Finished zipping to build\$(BuildFolder)\$(buildDate)-$(BuildZipFileName)" Importance="high" />
</Target>
<Target Name="CreateSystemFolders" DependsOnTargets="CopyBelleBuild" Inputs="@(SystemFolders)" Outputs="%(Identity).Dummy">
<MakeDir Directories="@(SystemFolders)" />
</Target>
@@ -231,7 +231,7 @@
DestinationFiles="@(CustomLanguageFiles->'$(ConfigsFolder)Lang\%(RecursiveDir)%(Filename)%(Extension)')"
OverwriteReadOnlyFiles="true"
SkipUnchangedFiles="false" />
<Copy SourceFiles="@(WebConfigTransformFile)"
DestinationFiles="$(ConfigsFolder)Web.config.transform"
OverwriteReadOnlyFiles="true"
@@ -264,12 +264,14 @@
<Message Text="Compiling web project to build\$(BuildFolder)" Importance="high" />
<!-- For UseWPP_CopyWebApplication=True see http://stackoverflow.com/questions/1983575/copywebapplication-with-web-config-transformations -->
<!-- Build the Umbraco.Web.UI project -->
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath);Verbosity=minimal" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False" />
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath);Verbosity=minimal" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False">
</MSBuild>
<!-- DONE -->
<Message Text="Finished compiling projects" Importance="high" />
</Target>
<Target Name="SetVersionNumber" Condition="'$(BUILD_RELEASE)'!=''">
<PropertyGroup>
<NewVersion>$(BUILD_RELEASE)</NewVersion>
@@ -305,34 +307,34 @@
ReplacementText="AssemblyFileVersion(&quot;$(BUILD_RELEASE)&quot;)"/>
<!--This updates the AssemblyInformationalVersion for the solution to the umbraco version and comment-->
<FileUpdate
<FileUpdate
Condition="'$(BUILD_COMMENT)'!=''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)-$(BUILD_COMMENT)&quot;)"/>
<FileUpdate
<FileUpdate
Condition="'$(BUILD_COMMENT)'==''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)&quot;)"/>
<FileUpdate
<FileUpdate
Condition="'$(BUILD_NIGHTLY)'!=''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)-$(BUILD_NIGHTLY)&quot;)"/>
<FileUpdate
<FileUpdate
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)&quot;)"/>
<FileUpdate
<FileUpdate
Condition="'$(BUILD_COMMENT)'=='' AND '$(BUILD_NIGHTLY)'==''"
Files="..\src\SolutionInfo.cs"
Regex="AssemblyInformationalVersion\(&quot;(.+)?&quot;\)"
ReplacementText="AssemblyInformationalVersion(&quot;$(BUILD_RELEASE)&quot;)"/>
<!--This updates the copyright year-->
<FileUpdate
<FileUpdate
Files="..\src\SolutionInfo.cs"
Regex="AssemblyCopyright\(&quot;Copyright © Umbraco (\d{4})&quot;\)"
ReplacementText="AssemblyCopyright(&quot;Copyright © Umbraco $([System.DateTime]::Now.ToString(`yyyy`))&quot;)"/>
-1
View File
@@ -23,7 +23,6 @@ ECHO Change directory to %CD%\..\src\Umbraco.Web.UI.Client\
CD %CD%\..\src\Umbraco.Web.UI.Client\
ECHO Do npm install and the grunt build of Belle
call npm cache clean --quiet
call npm install --quiet
call npm install -g grunt-cli --quiet
call npm install -g bower --quiet
+12 -43
View File
@@ -2,32 +2,22 @@
SETLOCAL
REM SETLOCAL is on, so changes to the path not persist to the actual user's path
git.exe --version
IF %ERRORLEVEL%==9009 GOTO :trydefaultpath
REM OK, DONE
git.exe 2> NUL
if %ERRORLEVEL%==9009 GOTO :trydefaultpath
GOTO :EOF
:trydefaultpath
PATH=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH%
git.exe --version
IF %ERRORLEVEL%==9009 GOTO :showerror
REM OK, DONE
path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH%
git.exe 2> NUL
if %ERRORLEVEL%==9009 GOTO :showerror
GOTO :EOF
:showerror
ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd
SET /p install=" Do you want to install Git through Chocolatey [y/n]? " %=%
IF %install%==y (
:: Create a temporary batch file to execute either after elevating to admin or as-is when the user is already admin
ECHO @ECHO OFF > "%temp%\ChocoInstallGit.cmd"
ECHO SETLOCAL >> "%temp%\ChocoInstallGit.cmd"
ECHO ECHO Installing Chocolatey first >> "%temp%\ChocoInstallGit.cmd"
ECHO @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" >> "%temp%\ChocoInstallGit.cmd"
ECHO SET PATH=%%PATH%%;%%ALLUSERSPROFILE%%\chocolatey\bin >> "%temp%\ChocoInstallGit.cmd"
ECHO choco install git -y >> "%temp%\ChocoInstallGit.cmd"
set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=%
if %install%==y (
GOTO :installgit
) ELSE (
) else (
GOTO :cantcontinue
)
@@ -36,28 +26,7 @@ ECHO Can't complete the build without Git being in the path. Please add it to be
GOTO :EOF
:installgit
pushd %~dp0
:: Running prompt elevated
:: --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
:: --> If error flag set, we do not have admin.
IF '%errorlevel%' NEQ '0' (
GOTO UACPrompt
) ELSE ( GOTO gotAdmin )
:UACPrompt
ECHO You're not currently running this with admin privileges, we'll now try to execute the install of Git through Chocolatey after elevating to admin privileges
ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
ECHO UAC.ShellExecute "%temp%\ChocoInstallGit.cmd", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
EXIT /B
:gotAdmin
IF EXIST "%temp%\getadmin.vbs" ( DEL "%temp%\getadmin.vbs" )
pushd "%CD%"
CD /D "%~dp0"
CALL "%temp%\ChocoInstallGit.cmd"
ECHO Installing Chocolatey first
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
ECHO Installing Git through Chocolatey
choco install git
+7 -10
View File
@@ -27,19 +27,16 @@
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.9.8, 7.0.0)" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
<dependency id="ClientDependency" version="[1.8.4, 2.0.0)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 2.0.0)" />
<!-- AutoMapper can not be updated due to: https://github.com/AutoMapper/AutoMapper/issues/373#issuecomment-127644405 -->
<dependency id="AutoMapper" version="[3.3.1, 4.0.0)" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
<dependency id="Examine" version="[0.1.70, 1.0.0)" />
<dependency id="ImageProcessor" version="[2.4.5, 3.0.0)" />
<dependency id="ImageProcessor.Web" version="[4.6.6, 5.0.0)" />
<dependency id="AutoMapper" version="[3.0.0, 3.1.0)" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 9.0.0)" />
<dependency id="Examine" version="[0.1.68, 1.0.0)" />
<dependency id="ImageProcessor" version="[2.3.3, 3.0.0)" />
<dependency id="ImageProcessor.Web" version="[4.5.3, 5.0.0)" />
<dependency id="semver" version="[1.1.2, 2.0.0)" />
<dependency id="UrlRewritingNet" version="[2.0.7, 3.0.0)" />
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
<dependency id="Markdown" version="[1.14.4, 2.0.0)" />
<dependency id="System.Threading.Tasks.Dataflow" version="[4.6.0, 5.0.0)" />
<dependency id="Markdown" version="[1.14.4, 2.0.0)" />
</dependencies>
</metadata>
<files>
+3 -5
View File
@@ -16,9 +16,8 @@
<tags>umbraco</tags>
<dependencies>
<dependency id="UmbracoCms.Core" version="[$version$]" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 9.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.2, 4.0.0)" />
</dependencies>
</metadata>
<files>
@@ -33,7 +32,6 @@
<file src="..\_BuildOutput\WebApp\config\splashes\**" target="UmbracoFiles\Config\splashes" />
<file src="..\_BuildOutput\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
<file src="..\_BuildOutput\WebApp\umbraco_client\**" target="UmbracoFiles\umbraco_client" />
<file src="..\_BuildOutput\WebApp\Media\Web.config" target="Content\Media\Web.config" />
<file src="tools\install.ps1" target="tools\install.ps1" />
<file src="tools\Readme.txt" target="tools\Readme.txt" />
<file src="tools\ReadmeUpgrade.txt" target="tools\ReadmeUpgrade.txt" />
@@ -46,4 +44,4 @@
<file src="tools\Views.Web.config.install.xdt" target="Views\Web.config.install.xdt" />
<file src="build\**" target="build" />
</files>
</package>
</package>
@@ -21,12 +21,6 @@
</section>
<section alias="StartupDeveloperDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
<areas xdt:Transform="InsertIfMissing">
<area xdt:Transform="InsertIfMissing">developer</area>
</areas>
</section>
<section alias="StartupDeveloperDashboardSection">
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Replace">
<control showOnce="true" addPanel="true" panelCaption="">
views/dashboard/developer/developerdashboardvideos.html
@@ -36,21 +30,16 @@
</section>
<section alias="StartupDeveloperDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
<tab caption="Examine Management" xdt:Locator="Match(caption)" xdt:Transform="InsertIfMissing">
<tab caption="Examine Management" xdt:Locator="Match(caption)" xdt:Transform="InsertIfMissing">
<control>
views/dashboard/developer/examinemanagement.html
</control>
</tab>
<tab caption="Health Check" xdt:Transform="InsertIfMissing" xdt:Locator="Match(caption)">
</tab>
<tab caption="Xml Data Integrity Report" xdt:Transform="InsertIfMissing">
<control>
views/dashboard/developer/healthcheck.html
views/dashboard/developer/xmldataintegrityreport.html
</control>
</tab>
<tab caption="Redirect URL Management" xdt:Transform="InsertIfMissing" xdt:Locator="Match(caption)">
<control>
views/dashboard/developer/redirecturls.html
</control>
</tab>
</tab>
</section>
<section alias="StartupMediaDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
@@ -63,6 +52,19 @@
</tab>
</section>
<section alias="StartupDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
<tab caption="Get Started" xdt:Transform="Insert">
<access>
<grant>admin</grant>
</access>
<control showOnce="true" addPanel="true" panelCaption="">
views/dashboard/default/startupdashboardintro.html
</control>
</tab>
<tab caption="Last Edits" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
</section>
<section alias="StartupMemberDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
<tab caption="Get Started" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
<tab caption="Get Started" xdt:Transform="Insert">
@@ -78,6 +80,5 @@
<section alias="StartupDashboardSection">
<tab caption="Change Password" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
<tab caption="Last Edits" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
</section>
</dashBoard>
+1 -8
View File
@@ -292,8 +292,6 @@
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
<remove fileExtension=".woff" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
<mimeMap fileExtension=".woff" mimeType="application/x-font-woff" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
<remove fileExtension=".woff2" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
<mimeMap fileExtension=".woff2" mimeType="application/x-font-woff2" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
<remove fileExtension=".less" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
<mimeMap fileExtension=".less" mimeType="text/css" xdt:Locator="Match(fileExtension)" xdt:Transform="InsertIfMissing" />
</staticContent>
@@ -319,7 +317,6 @@
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='HtmlAgilityPack')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='AutoMapper')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Net.Http')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Newtonsoft.Json')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Web.Mvc')" xdt:Transform="Remove" />
@@ -335,14 +332,10 @@
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Insert">
<dependentAssembly>
<assemblyIdentity name="HtmlAgilityPack" publicKeyToken="bd319b19eaf3b43a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.4.9.0" newVersion="1.4.9.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="AutoMapper" publicKeyToken="be96cd2c38ef1005" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.3.1.0" newVersion="3.3.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
+1 -6
View File
@@ -97,12 +97,7 @@ if ($project) {
$umbracoUIXMLSource = Join-Path $installPath "UmbracoFiles\Umbraco\Config\Create\UI.xml"
$umbracoUIXMLDestination = Join-Path $projectPath "Umbraco\Config\Create\UI.xml"
Copy-Item $umbracoUIXMLSource $umbracoUIXMLDestination -Force
} else {
$upgradeViewSource = Join-Path $umbracoFolderSource "Views\install\*"
$upgradeView = Join-Path $umbracoFolder "Views\install\"
Write-Host "Copying2 ${upgradeViewSource} to ${upgradeView}"
Copy-Item $upgradeViewSource $upgradeView -Force
}
}
$installFolder = Join-Path $projectPath "Install"
if(Test-Path $installFolder) {
+9 -17
View File
@@ -55,29 +55,21 @@
xdt:Transform="SetAttributes()" />
<!--Developer-->
<add alias="packager" application="developer"
<add alias="datatype" application="developer"
xdt:Locator="Match(application,alias)"
xdt:Transform="Remove" />
<add alias="packagerPackages" application="developer"
<add initialize="true" sortOrder="0" alias="datatypes" application="developer" title="Data Types" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco"
xdt:Locator="Match(application,alias)"
xdt:Transform="Remove" />
<add initialize="true" sortOrder="0" alias="packager" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.PackagesTreeController, umbraco"
xdt:Locator="Match(application,alias)"
xdt:Transform="InsertIfMissing" />
<!-- Before 7.4 this tree had the alias 'dataType', without the 's' on the end, this is here to rename it -->
<add sortOrder="1" alias="dataTypes" application="developer" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco"
xdt:Locator="Match(application,type)"
xdt:Transform="SetAttributes(alias,sortOrder)" />
<!-- Yes, set the sortOrder again, like above because.. sometimes apparently we already have a dataTypes node and we can't remove more than one.. if they're equal though (same alias,application and sortOrder) it doesn't throw an error -->
<add sortOrder="1" alias="dataTypes" application="developer"
xdt:Locator="Match(application,alias)"
xdt:Transform="SetAttributes(sortOrder)" />
xdt:Transform="InsertIfMissing" />
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2"
xdt:Locator="Match(application,alias)"
xdt:Transform="SetAttributes()" />
<add application="developer" alias="packager" title="Packages" type="umbraco.loadPackager, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="3"
xdt:Locator="Match(application,alias)"
xdt:Transform="SetAttributes()" />
<add application="developer" alias="packagerPackages" title="Packager Packages" type="umbraco.loadPackages, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" initialize="false" sortOrder="3"
xdt:Locator="Match(application,alias)"
xdt:Transform="SetAttributes()" />
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
xdt:Locator="Match(application,alias)"
xdt:Transform="SetAttributes()" />
+2 -3
View File
@@ -1,3 +1,2 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.6.0
alpha038
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.5.0
Binary file not shown.
+20 -28
View File
@@ -40,10 +40,15 @@ namespace SqlCE4Umbraco
var localConnection = new SqlCeConnection(ConnectionString);
if (!System.IO.File.Exists(ReplaceDataDirectory(localConnection.Database)))
{
using (var sqlCeEngine = new SqlCeEngine(ConnectionString))
{
sqlCeEngine.CreateDatabase();
}
var sqlCeEngine = new SqlCeEngine(ConnectionString);
sqlCeEngine.CreateDatabase();
// SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here
// since it's been like this for quite some time
//using (var sqlCeEngine = new SqlCeEngine(ConnectionString))
//{
// sqlCeEngine.CreateDatabase();
//}
}
}
@@ -180,16 +185,12 @@ namespace SqlCE4Umbraco
/// <returns>The return value of the command.</returns>
protected override object ExecuteScalar(string commandText, SqlCeParameter[] parameters)
{
#if DEBUG && DebugDataLayer
#if DEBUG && DebugDataLayer
// Log Query Execution
Trace.TraceInformation(GetType().Name + " SQL ExecuteScalar: " + commandText);
#endif
using (var cc = UseCurrentConnection)
{
return SqlCeApplicationBlock.ExecuteScalar(
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
CommandType.Text, commandText, parameters);
}
#endif
return SqlCeApplicationBlock.ExecuteScalar(ConnectionString, CommandType.Text, commandText, parameters);
}
/// <summary>
@@ -202,17 +203,12 @@ namespace SqlCE4Umbraco
/// </returns>
protected override int ExecuteNonQuery(string commandText, SqlCeParameter[] parameters)
{
#if DEBUG && DebugDataLayer
#if DEBUG && DebugDataLayer
// Log Query Execution
Trace.TraceInformation(GetType().Name + " SQL ExecuteNonQuery: " + commandText);
#endif
#endif
using (var cc = UseCurrentConnection)
{
return SqlCeApplicationBlock.ExecuteNonQuery(
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
CommandType.Text, commandText, parameters);
}
return SqlCeApplicationBlock.ExecuteNonQuery(ConnectionString, CommandType.Text, commandText, parameters);
}
/// <summary>
@@ -225,17 +221,13 @@ namespace SqlCE4Umbraco
/// </returns>
protected override IRecordsReader ExecuteReader(string commandText, SqlCeParameter[] parameters)
{
#if DEBUG && DebugDataLayer
#if DEBUG && DebugDataLayer
// Log Query Execution
Trace.TraceInformation(GetType().Name + " SQL ExecuteReader: " + commandText);
#endif
#endif
using (var cc = UseCurrentConnection)
{
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
CommandType.Text, commandText, parameters));
}
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(ConnectionString, CommandType.Text,
commandText, parameters));
}
+22 -24
View File
@@ -21,15 +21,15 @@ namespace SqlCE4Umbraco
public class SqlCEInstaller : DefaultInstallerUtility<SqlCEHelper>
{
#region Private Constants
/// <summary>The latest database version this installer supports.</summary>
private const DatabaseVersion LatestVersionSupported = DatabaseVersion.Version4_8;
/// <summary>The specifications to determine the database version.</summary>
private static readonly VersionSpecs[] m_VersionSpecs = new VersionSpecs[] {
new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8),
new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1),
new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4),
new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8),
new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1),
new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4),
new VersionSpecs("SELECT description FROM cmsContentType",DatabaseVersion.Version3),
new VersionSpecs("SELECT id FROM sysobjects",DatabaseVersion.None) };
@@ -43,9 +43,8 @@ namespace SqlCE4Umbraco
public override bool CanConnect
{
get
{
using (var sqlHelper = SqlHelper)
sqlHelper.CreateEmptyDatabase();
{
SqlHelper.CreateEmptyDatabase();
return base.CanConnect;
}
}
@@ -94,22 +93,22 @@ namespace SqlCE4Umbraco
#region DefaultInstaller Members
/// <summary>
/// Returns the sql to do a full install
/// </summary>
protected override string FullInstallSql
{
get { return string.Empty; }
}
/// <summary>
/// Returns the sql to do a full install
/// </summary>
protected override string FullInstallSql
{
get { return string.Empty; }
}
/// <summary>
/// Returns the sql to do an upgrade
/// </summary>
protected override string UpgradeSql
{
get { return string.Empty; }
}
/// <summary>
/// Returns the sql to do an upgrade
/// </summary>
protected override string UpgradeSql
{
get { return string.Empty; }
}
// We need to override this as the default way of detection a db connection checks for systables that doesn't exist
// in a CE db
@@ -124,9 +123,8 @@ namespace SqlCE4Umbraco
// verify connection
try
{
using (var sqlHelper = SqlHelper)
if (SqlCeApplicationBlock.VerifyConnection(sqlHelper.ConnectionString))
return DatabaseVersion.None;
if (SqlCeApplicationBlock.VerifyConnection(base.SqlHelper.ConnectionString))
return DatabaseVersion.None;
}
catch (Exception e)
{
+11 -17
View File
@@ -37,15 +37,13 @@ namespace SqlCE4Umbraco
ITable table = null;
// get name in correct casing
using (var sqlHelper = SqlHelper)
name = sqlHelper.ExecuteScalar<string>("SELECT name FROM sys.tables WHERE name=@name",
sqlHelper.CreateParameter("name", name));
name = SqlHelper.ExecuteScalar<string>("SELECT name FROM sys.tables WHERE name=@name",
SqlHelper.CreateParameter("name", name));
if (name != null)
{
table = new DefaultTable(name);
using (var sqlHelper = SqlHelper)
using (IRecordsReader reader = sqlHelper.ExecuteReader(
using (IRecordsReader reader = SqlHelper.ExecuteReader(
@"SELECT c.name AS Name, st.name AS DataType, c.max_length, c.is_nullable, c.is_identity
FROM sys.tables AS t
JOIN sys.columns AS c ON t.object_id = c.object_id
@@ -53,7 +51,7 @@ namespace SqlCE4Umbraco
JOIN sys.types AS ty ON ty.user_type_id = c.user_type_id
JOIN sys.types st ON ty.system_type_id = st.user_type_id
WHERE t.name = @name
ORDER BY c.column_id", sqlHelper.CreateParameter("name", name)))
ORDER BY c.column_id", SqlHelper.CreateParameter("name", name)))
{
while (reader.Read())
{
@@ -114,8 +112,7 @@ namespace SqlCE4Umbraco
// create query
StringBuilder createTableQuery = new StringBuilder();
using (var sqlHelper = SqlHelper)
createTableQuery.AppendFormat("CREATE TABLE [{0}] (", sqlHelper.EscapeString(table.Name));
createTableQuery.AppendFormat("CREATE TABLE [{0}] (", SqlHelper.EscapeString(table.Name));
// add fields
while (hasNext)
@@ -139,8 +136,7 @@ namespace SqlCE4Umbraco
// execute query
try
{
using (var sqlHelper = SqlHelper)
sqlHelper.ExecuteNonQuery(createTableQuery.ToString());
SqlHelper.ExecuteNonQuery(createTableQuery.ToString());
}
catch (Exception executeException)
{
@@ -158,15 +154,13 @@ namespace SqlCE4Umbraco
Debug.Assert(table != null && field != null);
StringBuilder addColumnQuery = new StringBuilder();
using (var sqlHelper = SqlHelper)
addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}",
sqlHelper.EscapeString(table.Name),
sqlHelper.EscapeString(field.Name),
sqlHelper.EscapeString(GetDatabaseType(field)));
addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}",
SqlHelper.EscapeString(table.Name),
SqlHelper.EscapeString(field.Name),
SqlHelper.EscapeString(GetDatabaseType(field)));
try
{
using (var sqlHelper = SqlHelper)
sqlHelper.ExecuteNonQuery(addColumnQuery.ToString());
SqlHelper.ExecuteNonQuery(addColumnQuery.ToString());
}
catch (Exception executeException)
{
+93 -175
View File
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlServerCe;
using System.Data;
using System.Diagnostics;
@@ -23,60 +25,29 @@ namespace SqlCE4Umbraco
string commandText,
params SqlCeParameter[] commandParameters
)
{
try
{
using (var conn = SqlCeContextGuardian.Open(connectionString))
{
return ExecuteScalarTry(conn, null, commandText, commandParameters);
}
}
catch (Exception ee)
{
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee);
}
}
public static object ExecuteScalar(
SqlCeConnection conn, SqlCeTransaction trx,
CommandType commandType,
string commandText,
params SqlCeParameter[] commandParameters)
{
try
{
return ExecuteScalarTry(conn, trx, commandText, commandParameters);
}
catch (Exception ee)
{
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee);
}
}
public static object ExecuteScalar(
SqlCeConnection conn,
CommandType commandType,
string commandText,
params SqlCeParameter[] commandParameters)
{
return ExecuteScalar(conn, null, commandType, commandText, commandParameters);
}
private static object ExecuteScalarTry(
SqlCeConnection conn, SqlCeTransaction trx,
string commandText,
params SqlCeParameter[] commandParameters)
{
object retVal;
using (var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx))
try
{
AttachParameters(cmd, commandParameters);
Debug.WriteLine("---------------------------------SCALAR-------------------------------------");
Debug.WriteLine(commandText);
Debug.WriteLine("----------------------------------------------------------------------------");
retVal = cmd.ExecuteScalar();
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
{
using (SqlCeCommand cmd = new SqlCeCommand(commandText, conn))
{
AttachParameters(cmd, commandParameters);
Debug.WriteLine("---------------------------------SCALAR-------------------------------------");
Debug.WriteLine(commandText);
Debug.WriteLine("----------------------------------------------------------------------------");
retVal = cmd.ExecuteScalar();
}
}
return retVal;
}
catch (Exception ee)
{
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
}
return retVal;
}
/// <summary>
@@ -95,10 +66,49 @@ namespace SqlCE4Umbraco
{
try
{
using (var conn = SqlCeContextGuardian.Open(connectionString))
int rowsAffected;
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
{
return ExecuteNonQueryTry(conn, null, commandText, commandParameters);
// this is for multiple queries in the installer
if (commandText.Trim().StartsWith("!!!"))
{
commandText = commandText.Trim().Trim('!');
string[] commands = commandText.Split('|');
string currentCmd = String.Empty;
foreach (string cmd in commands)
{
try
{
currentCmd = cmd;
if (!String.IsNullOrWhiteSpace(cmd))
{
SqlCeCommand c = new SqlCeCommand(cmd, conn);
c.ExecuteNonQuery();
}
}
catch (Exception e)
{
Debug.WriteLine("*******************************************************************");
Debug.WriteLine(currentCmd);
Debug.WriteLine(e);
Debug.WriteLine("*******************************************************************");
}
}
return 1;
}
else
{
Debug.WriteLine("----------------------------------------------------------------------------");
Debug.WriteLine(commandText);
Debug.WriteLine("----------------------------------------------------------------------------");
SqlCeCommand cmd = new SqlCeCommand(commandText, conn);
AttachParameters(cmd, commandParameters);
rowsAffected = cmd.ExecuteNonQuery();
}
}
return rowsAffected;
}
catch (Exception ee)
{
@@ -106,74 +116,6 @@ namespace SqlCE4Umbraco
}
}
public static int ExecuteNonQuery(
SqlCeConnection conn,
CommandType commandType,
string commandText,
params SqlCeParameter[] commandParameters
)
{
return ExecuteNonQuery(conn, null, commandType, commandText, commandParameters);
}
public static int ExecuteNonQuery(
SqlCeConnection conn, SqlCeTransaction trx,
CommandType commandType,
string commandText,
params SqlCeParameter[] commandParameters
)
{
try
{
return ExecuteNonQueryTry(conn, trx, commandText, commandParameters);
}
catch (Exception ee)
{
throw new SqlCeProviderException("Error running NonQuery: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
}
}
private static int ExecuteNonQueryTry(
SqlCeConnection conn, SqlCeTransaction trx,
string commandText,
params SqlCeParameter[] commandParameters)
{
// this is for multiple queries in the installer
if (commandText.Trim().StartsWith("!!!"))
{
commandText = commandText.Trim().Trim('!');
var commands = commandText.Split('|');
var currentCmd = string.Empty;
foreach (var command in commands)
{
try
{
currentCmd = command;
if (string.IsNullOrWhiteSpace(command)) continue;
var c = trx == null ? new SqlCeCommand(command, conn) : new SqlCeCommand(command, conn, trx);
c.ExecuteNonQuery();
}
catch (Exception e)
{
Debug.WriteLine("*******************************************************************");
Debug.WriteLine(currentCmd);
Debug.WriteLine(e);
Debug.WriteLine("*******************************************************************");
}
}
return 1;
}
Debug.WriteLine("----------------------------------------------------------------------------");
Debug.WriteLine(commandText);
Debug.WriteLine("----------------------------------------------------------------------------");
var cmd = new SqlCeCommand(commandText, conn);
AttachParameters(cmd, commandParameters);
var rowsAffected = cmd.ExecuteNonQuery();
return rowsAffected;
}
/// <summary>
///
/// </summary>
@@ -191,8 +133,25 @@ namespace SqlCE4Umbraco
{
try
{
var conn = SqlCeContextGuardian.Open(connectionString);
return ExecuteReaderTry(conn, null, commandText, commandParameters);
Debug.WriteLine("---------------------------------READER-------------------------------------");
Debug.WriteLine(commandText);
Debug.WriteLine("----------------------------------------------------------------------------");
SqlCeDataReader reader;
SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString);
try
{
SqlCeCommand cmd = new SqlCeCommand(commandText, conn);
AttachParameters(cmd, commandParameters);
reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch
{
conn.Close();
throw;
}
return reader;
}
catch (Exception ee)
{
@@ -200,71 +159,30 @@ namespace SqlCE4Umbraco
}
}
public static SqlCeDataReader ExecuteReader(
SqlCeConnection conn,
CommandType commandType,
string commandText,
params SqlCeParameter[] commandParameters
)
{
return ExecuteReader(conn, commandType, commandText, commandParameters);
}
public static SqlCeDataReader ExecuteReader(
SqlCeConnection conn, SqlCeTransaction trx,
CommandType commandType,
string commandText,
params SqlCeParameter[] commandParameters
)
{
try
{
return ExecuteReaderTry(conn, trx, commandText, commandParameters);
}
catch (Exception ee)
{
throw new SqlCeProviderException("Error running Reader: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
}
}
private static SqlCeDataReader ExecuteReaderTry(
SqlCeConnection conn, SqlCeTransaction trx,
string commandText,
params SqlCeParameter[] commandParameters)
{
Debug.WriteLine("---------------------------------READER-------------------------------------");
Debug.WriteLine(commandText);
Debug.WriteLine("----------------------------------------------------------------------------");
try
{
var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx);
AttachParameters(cmd, commandParameters);
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch
{
conn.Close();
throw;
}
}
public static bool VerifyConnection(string connectionString)
{
using (var conn = SqlCeContextGuardian.Open(connectionString))
bool isConnected = false;
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
{
return conn.State == ConnectionState.Open;
isConnected = conn.State == ConnectionState.Open;
}
return isConnected;
}
private static void AttachParameters(SqlCeCommand command, IEnumerable<SqlCeParameter> commandParameters)
private static void AttachParameters(SqlCeCommand command, SqlCeParameter[] commandParameters)
{
foreach (var parameter in commandParameters)
foreach (SqlCeParameter parameter in commandParameters)
{
if ((parameter.Direction == ParameterDirection.InputOutput) && (parameter.Value == null))
{
parameter.Value = DBNull.Value;
}
command.Parameters.Add(parameter);
}
}
}
}
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.6.0")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha038")]
[assembly: AssemblyFileVersion("7.5.0")]
[assembly: AssemblyInformationalVersion("7.5.0")]
+22 -8
View File
@@ -1,18 +1,22 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Profiling;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
namespace Umbraco.Core
{
/// <summary>
/// <summary>
/// the Umbraco Application context
/// </summary>
/// <remarks>
@@ -183,6 +187,7 @@ namespace Umbraco.Core
readonly ManualResetEventSlim _isReadyEvent = new ManualResetEventSlim(false);
private DatabaseContext _databaseContext;
private ServiceContext _services;
private PackageMigrationsContext _packageMigrationsContext;
public bool IsReady
{
@@ -212,17 +217,19 @@ namespace Umbraco.Core
public bool IsConfigured
{
get { return _configured.Value; }
get { return _umbracoVersionConfigured.Value; }
}
/// <summary>
/// If the db is configured, there is a database context and there is an umbraco schema, but we are not 'configured' , then it means we are upgrading
/// If the db is configured, there is a database context and there is an umbraco schema,
/// but we are: (not 'configured' OR there's pending migrations), then it means we are upgrading
/// </summary>
public bool IsUpgrading
{
get
{
if (IsConfigured == false
if ((IsConfigured == false || PackageMigrationsContext.HasPendingPackageMigrations)
&& DatabaseContext != null
&& DatabaseContext.IsDatabaseConfigured)
{
@@ -268,7 +275,8 @@ namespace Umbraco.Core
// ReSharper disable once InconsistentNaming
internal string _umbracoApplicationUrl;
private Lazy<bool> _configured;
private Lazy<bool> _umbracoVersionConfigured;
internal MainDom MainDom { get; private set; }
private void Init()
@@ -277,7 +285,7 @@ namespace Umbraco.Core
MainDom.Acquire();
//Create the lazy value to resolve whether or not the application is 'configured'
_configured = new Lazy<bool>(() =>
_umbracoVersionConfigured = new Lazy<bool>(() =>
{
try
{
@@ -318,9 +326,10 @@ namespace Umbraco.Core
LogHelper.Error<ApplicationContext>("Error determining if application is configured, returning false", ex);
return false;
}
});
});
}
_packageMigrationsContext = new PackageMigrationsContext(DatabaseContext, Services.MigrationEntryService, ProfilingLogger.Logger);
}
private string ConfigurationStatus
{
@@ -343,6 +352,11 @@ namespace Umbraco.Core
throw new Exception("ApplicationContext has already been initialized.");
}
internal PackageMigrationsContext PackageMigrationsContext
{
get { return _packageMigrationsContext; }
}
/// <summary>
/// Gets the current DatabaseContext
/// </summary>
-39
View File
@@ -1,39 +0,0 @@
namespace Umbraco.Core
{
public static class ByteArrayExtensions
{
private static readonly char[] BytesToHexStringLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static string ToHexString(this byte[] bytes)
{
int i = 0, p = 0, bytesLength = bytes.Length;
var chars = new char[bytesLength * 2];
while (i < bytesLength)
{
var b = bytes[i++];
chars[p++] = BytesToHexStringLookup[b / 0x10];
chars[p++] = BytesToHexStringLookup[b % 0x10];
}
return new string(chars, 0, chars.Length);
}
public static string ToHexString(this byte[] bytes, char separator, int blockSize, int blockCount)
{
int p = 0, bytesLength = bytes.Length, count = 0, size = 0;
var chars = new char[bytesLength * 2 + blockCount];
for (var i = 0; i < bytesLength; i++)
{
var b = bytes[i++];
chars[p++] = BytesToHexStringLookup[b / 0x10];
chars[p++] = BytesToHexStringLookup[b % 0x10];
if (count == blockCount) continue;
if (++size < blockSize) continue;
chars[p++] = '/';
size = 0;
count++;
}
return new string(chars, 0, chars.Length);
}
}
}
@@ -9,9 +9,6 @@ namespace Umbraco.Core.Cache
/// <summary>
/// A cache provider that caches items in the HttpContext.Items
/// </summary>
/// <remarks>
/// If the Items collection is null, then this provider has no effect
/// </remarks>
internal class HttpRequestCacheProvider : DictionaryCacheProviderBase
{
// context provider
@@ -37,11 +34,6 @@ namespace Umbraco.Core.Cache
get { return _context != null ? _context.Items : HttpContext.Current.Items; }
}
private bool HasContextItems
{
get { return (_context != null && _context.Items != null) || HttpContext.Current != null; }
}
// for unit tests
public HttpRequestCacheProvider(HttpContextBase context)
{
@@ -58,23 +50,18 @@ namespace Umbraco.Core.Cache
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
if (HasContextItems == false) return Enumerable.Empty<DictionaryEntry>();
return ContextItems.Cast<DictionaryEntry>()
.Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
}
protected override void RemoveEntry(string key)
{
if (HasContextItems == false) return;
ContextItems.Remove(key);
}
protected override object GetEntry(string key)
{
return HasContextItems ? ContextItems[key] : null;
return ContextItems[key];
}
#region Lock
@@ -94,9 +81,7 @@ namespace Umbraco.Core.Cache
get
{
return HasContextItems
? (IDisposable) new MonitorLock(ContextItems.SyncRoot)
: new NoopLocker();
return new MonitorLock(ContextItems.SyncRoot);
}
}
@@ -106,9 +91,6 @@ namespace Umbraco.Core.Cache
public override object GetCacheItem(string cacheKey, Func<object> getCacheItem)
{
//no place to cache so just return the callback result
if (HasContextItems == false) return getCacheItem();
cacheKey = GetCacheKey(cacheKey);
Lazy<object> result;
@@ -146,10 +128,5 @@ namespace Umbraco.Core.Cache
#region Insert
#endregion
private class NoopLocker : DisposableObject
{
protected override void DisposeResources()
{ }
}
}
}
@@ -154,7 +154,7 @@ namespace Umbraco.Core.Cache
value = result.Value; // will not throw (safe lazy)
var eh = value as ExceptionHolder;
if (eh != null) throw new Exception("Exception while creating a value.", eh.Exception); // throw once!
if (eh != null) throw eh.Exception; // throw once!
return value;
}
+21 -12
View File
@@ -5,31 +5,38 @@ using System.Web.Caching;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Represents a cache provider that does not cache anything.
/// </summary>
public class NullCacheProvider : IRuntimeCacheProvider
internal class NullCacheProvider : IRuntimeCacheProvider
{
public virtual void ClearAllCache()
{ }
{
}
public virtual void ClearCacheItem(string key)
{ }
{
}
public virtual void ClearCacheObjectTypes(string typeName)
{ }
{
}
public virtual void ClearCacheObjectTypes<T>()
{ }
{
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{ }
{
}
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{ }
{
}
public virtual void ClearCacheByKeyExpression(string regexString)
{ }
{
}
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
@@ -57,6 +64,8 @@ namespace Umbraco.Core.Cache
}
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{ }
{
}
}
}
@@ -1,23 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Caching;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web.Caching;
using Umbraco.Core.Logging;
using CacheItemPriority = System.Web.Caching.CacheItemPriority;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Represents a cache provider that caches item in a <see cref="MemoryCache"/>.
/// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache
/// </summary>
/// <remarks>The <see cref="MemoryCache"/> is created with name "in-memory". That name is
/// used to retrieve configuration options. It does not identify the memory cache, i.e.
/// each instance of this class has its own, independent, memory cache.</remarks>
public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
internal ObjectCache MemoryCache;
@@ -3,13 +3,14 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Caching;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Represents a cache provider that statically caches item in a concurrent dictionary.
/// A cache provider that statically caches everything in an in memory dictionary
/// </summary>
public class StaticCacheProvider : ICacheProvider
internal class StaticCacheProvider : ICacheProvider
{
internal readonly ConcurrentDictionary<string, object> StaticCache = new ConcurrentDictionary<string, object>();
@@ -74,6 +75,7 @@ namespace Umbraco.Core.Cache
public virtual object GetCacheItem(string cacheKey, Func<object> getCacheItem)
{
return StaticCache.GetOrAdd(cacheKey, key => getCacheItem());
}
}
}
}
@@ -291,19 +291,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
true);
}
}
[ConfigurationProperty("EnableInheritedMediaTypes")]
internal InnerTextConfigurationElement<bool> EnableInheritedMediaTypes
{
get
{
return new OptionalInnerTextConfigurationElement<bool>(
(InnerTextConfigurationElement<bool>)this["EnableInheritedMediaTypes"],
//set the default
true);
}
}
string IContentSection.NotificationEmailAddress
{
get { return Notifications.NotificationEmailAddress; }
@@ -443,10 +431,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
get { return EnableInheritedDocumentTypes; }
}
bool IContentSection.EnableInheritedMediaTypes
{
get { return EnableInheritedMediaTypes; }
}
}
}
@@ -61,7 +61,5 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
string DefaultDocumentTypeProperty { get; }
bool EnableInheritedDocumentTypes { get; }
bool EnableInheritedMediaTypes { get; }
}
}
@@ -9,8 +9,6 @@
bool DisableAlternativeTemplates { get; }
bool DisableFindContentByIdPath { get; }
bool DisableRedirectUrlTracking { get; }
string UrlProviderMode { get; }
@@ -27,12 +27,6 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return (bool) base["disableFindContentByIdPath"]; }
}
[ConfigurationProperty("disableRedirectUrlTracking", DefaultValue = "false")]
public bool DisableRedirectUrlTracking
{
get { return (bool) base["disableRedirectUrlTracking"]; }
}
[ConfigurationProperty("urlProviderMode", DefaultValue = "AutoLegacy")]
public string UrlProviderMode
{
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.6.0");
private static readonly Version Version = new Version("7.5.0");
/// <summary>
/// Gets the current version of Umbraco.
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
public static string CurrentComment { get { return "alpha038"; } }
public static string CurrentComment { get { return ""; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
@@ -32,12 +32,7 @@ namespace Umbraco.Core.Configuration
public static SemVersion GetSemanticVersion()
{
return new SemVersion(
Current.Major,
Current.Minor,
Current.Build,
CurrentComment.IsNullOrWhiteSpace() ? null : CurrentComment,
Current.Revision > 0 ? Current.Revision.ToInvariantString() : null);
return Current.GetSemanticVersion();
}
}
}
@@ -73,11 +73,6 @@
/// </summary>
public const string DataTypes = "dataTypes";
/// <summary>
/// alias for the packages tree
/// </summary>
public const string Packages = "packager";
/// <summary>
/// alias for the dictionary tree.
/// </summary>
@@ -122,11 +122,6 @@ namespace Umbraco.Core
/// MediaType alias for an image.
/// </summary>
public const string Image = "Image";
/// <summary>
/// MediaType alias indicating allowing auto-selection.
/// </summary>
public const string AutoSelect = "umbracoAutoSelect";
}
/// <summary>
+7 -20
View File
@@ -1,23 +1,15 @@
namespace Umbraco.Core
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core
{
public static partial class Constants
{
public static class Examine
{
/// <summary>
/// The alias of the internal member indexer
/// </summary>
public const string InternalMemberIndexer = "InternalMemberIndexer";
/// <summary>
/// The alias of the internal content indexer
/// </summary>
public const string InternalIndexer = "InternalIndexer";
/// <summary>
/// The alias of the external content indexer
/// </summary>
public const string ExternalIndexer = "ExternalIndexer";
/// <summary>
/// The alias of the internal member searcher
/// </summary>
@@ -27,11 +19,6 @@
/// The alias of the internal content searcher
/// </summary>
public const string InternalSearcher = "InternalSearcher";
/// <summary>
/// The alias of the external content searcher
/// </summary>
public const string ExternalSearcher = "ExternalSearcher";
}
}
}
@@ -69,11 +69,6 @@ namespace Umbraco.Core
/// </summary>
public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972";
/// <summary>
/// Guid for a Document object.
/// </summary>
public static readonly Guid DocumentGuid = new Guid(Document);
/// <summary>
/// Guid for a Document Type object.
/// </summary>
+24 -28
View File
@@ -1,37 +1,33 @@
namespace Umbraco.Core
{
public static partial class Constants
{
/// <summary>
/// Defines the identifiers for Umbraco system nodes.
/// </summary>
public static class System
{
/// <summary>
/// The integer identifier for global system root node.
/// </summary>
public const int Root = -1;
public static partial class Constants
{
/// <summary>
/// Defines the identifiers for Umbraco system nodes.
/// </summary>
public static class System
{
/// <summary>
/// The integer identifier for global system root node.
/// </summary>
public const int Root = -1;
/// <summary>
/// The integer identifier for content's recycle bin.
/// </summary>
public const int RecycleBinContent = -20;
/// <summary>
/// The integer identifier for content's recycle bin.
/// </summary>
public const int RecycleBinContent = -20;
/// <summary>
/// The integer identifier for media's recycle bin.
/// </summary>
public const int RecycleBinMedia = -21;
/// <summary>
/// The integer identifier for media's recycle bin.
/// </summary>
public const int RecycleBinMedia = -21;
public const int DefaultContentListViewDataTypeId = -95;
public const int DefaultContentListViewDataTypeId = -95;
public const int DefaultMediaListViewDataTypeId = -96;
public const int DefaultMembersListViewDataTypeId = -97;
}
public static class DatabaseProviders
{
public const string SqlCe = "System.Data.SqlServerCe.4.0";
public const string SqlServer = "System.Data.SqlClient";
public const string MySql = "MySql.Data.MySqlClient";
}
}
// identifiers for lock objects
public const int ServersLock = -331;
}
}
}
+17 -17
View File
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading;
@@ -31,13 +30,12 @@ using Umbraco.Core.Manifest;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Core.Strings;
using IntegerValidator = Umbraco.Core.PropertyEditors.IntegerValidator;
using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero;
namespace Umbraco.Core
{
/// <summary>
/// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application
/// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application
/// </summary>
/// <remarks>
/// This does not provide any startup functionality relating to web objects
@@ -193,14 +191,14 @@ namespace Umbraco.Core
protected virtual CacheHelper CreateApplicationCache()
{
var cacheHelper = new CacheHelper(
//we need to have the dep clone runtime cache provider to ensure
//we need to have the dep clone runtime cache provider to ensure
//all entities are cached properly (cloned in and cloned out)
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
new StaticCacheProvider(),
//we have no request based cache when not running in web-based context
new NullCacheProvider(),
new IsolatedRuntimeCache(type =>
//we need to have the dep clone runtime cache provider to ensure
//we need to have the dep clone runtime cache provider to ensure
//all entities are cached properly (cloned in and cloned out)
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
@@ -253,18 +251,18 @@ namespace Umbraco.Core
}
/// <summary>
/// Special method to initialize the ApplicationEventsResolver and any modifications required for it such
/// Special method to initialize the ApplicationEventsResolver and any modifications required for it such
/// as adding custom types to the resolver.
/// </summary>
protected virtual void InitializeApplicationEventsResolver()
{
//find and initialize the application startup handlers, we need to initialize this resolver here because
//it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to
//it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to
//events and to call their events during bootup.
//ApplicationStartupHandler.RegisterHandlers();
//... and set the special flag to let us resolve before frozen resolution
ApplicationEventsResolver.Current = new ApplicationEventsResolver(
ServiceProvider,
ServiceProvider,
ProfilingLogger.Logger,
PluginManager.ResolveApplicationStartupHandlers())
{
@@ -284,7 +282,7 @@ namespace Umbraco.Core
}
/// <summary>
/// Fires after initialization and calls the callback to allow for customizations to occur &
/// Fires after initialization and calls the callback to allow for customizations to occur &
/// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called
/// </summary>
/// <param name="afterStartup"></param>
@@ -336,7 +334,7 @@ namespace Umbraco.Core
{
if (_isComplete)
throw new InvalidOperationException("The boot manager has already been completed");
FreezeResolution();
//Here we need to make sure the db can be connected to
@@ -368,7 +366,7 @@ namespace Umbraco.Core
ProfilingLogger.Logger.Error<CoreBootManager>("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex);
throw;
}
});
});
}
//Now, startup all of our legacy startup handler
@@ -457,10 +455,6 @@ namespace Umbraco.Core
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar());
}
else if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"]))
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new SingleServerRegistrar());
}
else
{
ServerRegistrarResolver.Current = new ServerRegistrarResolver(
@@ -468,6 +462,7 @@ namespace Umbraco.Core
new Lazy<IServerRegistrationService>(() => ApplicationContext.Services.ServerRegistrationService),
new DatabaseServerRegistrarOptions()));
}
//by default we'll use the database server messenger with default options (no callbacks),
// this will be overridden in the web startup
@@ -478,7 +473,7 @@ namespace Umbraco.Core
ServiceProvider, ProfilingLogger.Logger,
() => PluginManager.ResolveAssignedMapperTypes());
//RepositoryResolver.Current = new RepositoryResolver(
// new RepositoryFactory(ApplicationCache));
@@ -505,7 +500,12 @@ namespace Umbraco.Core
//the database migration objects
MigrationResolver.Current = new MigrationResolver(
ProfilingLogger.Logger,
() => PluginManager.ResolveTypes<IMigration>());
() => PluginManager.ResolveTypes<IMigration>())
{
//This needs to be resolved before it's frozen because we need to find migrations to determine
// if Umbraco IsConfigured
CanResolveBeforeFrozen = true
};
// todo: remove once we drop IPropertyEditorValueConverter support.
PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver(
+108 -140
View File
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Configuration;
using System.Xml.Linq;
using Semver;
using Umbraco.Core.Configuration;
@@ -30,22 +30,17 @@ namespace Umbraco.Core
private readonly ILogger _logger;
private readonly SqlSyntaxProviders _syntaxProviders;
private bool _configured;
private readonly object _locker = new object();
private string _connectionString;
private string _providerName;
private DatabaseSchemaResult _result;
private DateTime? _connectionLastChecked = null;
/// <summary>
/// The number of minutes to throttle the checks to CanConnect
/// </summary>
private const int ConnectionCheckMinutes = 1;
[Obsolete("Use the constructor specifying all dependencies instead")]
public DatabaseContext(IDatabaseFactory factory)
: this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
{
new MySqlSyntaxProvider(LoggerResolver.Current.Logger),
new SqlCeSyntaxProvider(),
new SqlCeSyntaxProvider(),
new SqlServerSyntaxProvider()
}))
{
@@ -85,60 +80,21 @@ namespace Umbraco.Core
_configured = true;
}
#if DEBUG_DATABASES
public List<UmbracoDatabase> Databases
{
get
{
var factory = _factory as DefaultDatabaseFactory;
if (factory == null) throw new NotSupportedException();
return factory.Databases;
}
}
#endif
public ISqlSyntaxProvider SqlSyntax { get; private set; }
/// <summary>
/// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database.
/// Gets the <see cref="Database"/> object for doing CRUD operations
/// against custom tables that resides in the Umbraco database.
/// </summary>
/// <remarks>
/// <para>Should not be used for operation against standard Umbraco tables; as services should be used instead.</para>
/// <para>Gets or creates an "ambient" database that is either stored in http context + available for the whole
/// request + auto-disposed at the end of the request, or stored in call context if there is no http context - in which
/// case it *must* be explicitely disposed (which will remove it from call context).</para>
/// This should not be used for CRUD operations or queries against the
/// standard Umbraco tables! Use the Public services for that.
/// </remarks>
public virtual UmbracoDatabase Database
{
get { return _factory.CreateDatabase(); }
}
/// <summary>
/// Replaces the "ambient" database (if any) and by a new temp database.
/// </summary>
/// <remarks>
/// <para>The returned IDisposable *must* be diposed in order to properly dispose the temp database and
/// restore the original "ambient" database (if any).</para>
/// <para>This is to be used in background tasks to ensure that they have an "ambient" database which
/// will be properly removed from call context and does not interfere with anything else. In most case
/// it is not replacing anything, just temporarily installing a database in context.</para>
/// </remarks>
public virtual IDisposable UseSafeDatabase(bool force = false)
{
var factory = _factory as DefaultDatabaseFactory;
if (factory == null) throw new NotSupportedException();
if (DefaultDatabaseFactory.HasAmbientDatabase)
{
return force
? new UsingDatabase(DefaultDatabaseFactory.DetachAmbientDatabase(), factory.CreateDatabase())
: new UsingDatabase(null, null);
}
// create a new, temp, database (will be disposed with UsingDatabase)
return new UsingDatabase(null, factory.CreateDatabase());
}
/// <summary>
/// Boolean indicating whether the database has been configured
/// </summary>
@@ -154,28 +110,13 @@ namespace Umbraco.Core
{
get
{
if (IsDatabaseConfigured == false)
return false;
//Don't check again if the timeout period hasn't elapsed
//this ensures we don't keep checking the connection too many times in a row like during startup.
//Do check if the _connectionLastChecked is null which means we're just initializing or it could
//not connect last time it was checked.
if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes)
|| _connectionLastChecked.HasValue == false)
{
var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
LogHelper.Info<DatabaseContext>("CanConnect = " + canConnect);
_connectionLastChecked = canConnect == false ? null : (DateTime?) DateTime.Now;
return canConnect;
}
return _connectionLastChecked.HasValue;
if (IsDatabaseConfigured == false) return false;
var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
LogHelper.Info<DatabaseContext>("CanConnect = " + canConnect);
return canConnect;
}
}
/// <summary>
/// Gets the configured umbraco db connection string.
/// </summary>
@@ -194,7 +135,7 @@ namespace Umbraco.Core
if (string.IsNullOrEmpty(_providerName) == false)
return _providerName;
_providerName = Constants.DatabaseProviders.SqlServer;
_providerName = "System.Data.SqlClient";
if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null)
{
if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName) == false)
@@ -233,7 +174,7 @@ namespace Umbraco.Core
/// </summary>
public void ConfigureEmbeddedDatabaseConnection()
{
const string providerName = Constants.DatabaseProviders.SqlCe;
const string providerName = "System.Data.SqlServerCe.4.0";
var connectionString = GetEmbeddedDatabaseConnectionString();
SaveConnectionString(connectionString, providerName);
@@ -248,7 +189,7 @@ namespace Umbraco.Core
// since it's been like this for quite some time
//using (var engine = new SqlCeEngine(connectionString))
//{
// engine.CreateDatabase();
// engine.CreateDatabase();
//}
}
@@ -271,10 +212,10 @@ namespace Umbraco.Core
{
var provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString);
var databaseProvider = provider.ToString();
var providerName = Constants.DatabaseProviders.SqlServer;
var providerName = "System.Data.SqlClient";
if (databaseProvider.ToLower().Contains("mysql"))
{
providerName = Constants.DatabaseProviders.MySql;
providerName = "MySql.Data.MySqlClient";
}
SaveConnectionString(connectionString, providerName);
Initialize(string.Empty);
@@ -289,20 +230,20 @@ namespace Umbraco.Core
/// <param name="password">Database Password</param>
/// <param name="databaseProvider">Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql)</param>
public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider)
{
{
string providerName;
var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName);
SaveConnectionString(connectionString, providerName);
Initialize(providerName);
}
public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName)
{
providerName = Constants.DatabaseProviders.SqlServer;
providerName = "System.Data.SqlClient";
if (databaseProvider.ToLower().Contains("mysql"))
{
providerName = Constants.DatabaseProviders.MySql;
providerName = "MySql.Data.MySqlClient";
return string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password);
}
if (databaseProvider.ToLower().Contains("azure"))
@@ -319,7 +260,7 @@ namespace Umbraco.Core
/// <param name="databaseName">Name of the database</param>
public void ConfigureIntegratedSecurityDatabaseConnection(string server, string databaseName)
{
const string providerName = Constants.DatabaseProviders.SqlServer;
const string providerName = "System.Data.SqlClient";
var connectionString = GetIntegratedSecurityDatabaseConnectionString(server, databaseName);
SaveConnectionString(connectionString, providerName);
Initialize(providerName);
@@ -327,18 +268,18 @@ namespace Umbraco.Core
public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName)
{
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
}
internal string BuildAzureConnectionString(string server, string databaseName, string user, string password)
{
if (server.Contains(".") && ServerStartsWithTcp(server) == false)
server = string.Format("tcp:{0}", server);
if (server.Contains(".") == false && ServerStartsWithTcp(server))
{
string serverName = server.Contains(",")
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
string serverName = server.Contains(",")
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
: server;
var portAddition = string.Empty;
@@ -348,10 +289,10 @@ namespace Umbraco.Core
server = string.Format("{0}.database.windows.net{1}", serverName, portAddition);
}
if (ServerStartsWithTcp(server) == false)
server = string.Format("tcp:{0}.database.windows.net", server);
if (server.Contains(",") == false)
server = string.Format("{0},1433", server);
@@ -395,7 +336,7 @@ namespace Umbraco.Core
_connectionString = connectionString;
_providerName = providerName;
var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root));
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
@@ -432,7 +373,7 @@ namespace Umbraco.Core
var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName];
if (databaseSettings != null && string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) == false && string.IsNullOrWhiteSpace(databaseSettings.ProviderName) == false)
{
var providerName = Constants.DatabaseProviders.SqlServer;
var providerName = "System.Data.SqlClient";
string connString = null;
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName))
{
@@ -441,6 +382,7 @@ namespace Umbraco.Core
}
Initialize(providerName, connString);
DetermineSqlServerVersion();
}
else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false)
{
@@ -453,8 +395,8 @@ namespace Umbraco.Core
else if (legacyConnString.ToLowerInvariant().Contains("tcp:"))
{
//Must be sql azure
SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer);
Initialize(Constants.DatabaseProviders.SqlServer);
SaveConnectionString(legacyConnString, "System.Data.SqlClient");
Initialize("System.Data.SqlClient");
}
else if (legacyConnString.ToLowerInvariant().Contains("datalayer=mysql"))
{
@@ -465,19 +407,20 @@ namespace Umbraco.Core
foreach (var variable in legacyConnString.Split(';').Where(x => x.ToLowerInvariant().StartsWith("datalayer") == false))
connectionStringWithoutDatalayer = string.Format("{0}{1};", connectionStringWithoutDatalayer, variable);
SaveConnectionString(connectionStringWithoutDatalayer, Constants.DatabaseProviders.MySql);
Initialize(Constants.DatabaseProviders.MySql);
SaveConnectionString(connectionStringWithoutDatalayer, "MySql.Data.MySqlClient");
Initialize("MySql.Data.MySqlClient");
}
else
{
//Must be sql
SaveConnectionString(legacyConnString, Constants.DatabaseProviders.SqlServer);
Initialize(Constants.DatabaseProviders.SqlServer);
SaveConnectionString(legacyConnString, "System.Data.SqlClient");
Initialize("System.Data.SqlClient");
}
//Remove the legacy connection string, so we don't end up in a loop if something goes wrong.
GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName);
DetermineSqlServerVersion();
}
else
{
@@ -496,15 +439,15 @@ namespace Umbraco.Core
{
if (_syntaxProviders != null)
{
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
}
else if (SqlSyntax == null)
{
throw new InvalidOperationException("No " + typeof(ISqlSyntaxProvider) + " specified or no " + typeof(SqlSyntaxProviders) + " instance specified");
}
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
_configured = true;
}
catch (Exception e)
@@ -522,6 +465,49 @@ namespace Umbraco.Core
Initialize(providerName);
}
/// <summary>
/// Set the lazy resolution of determining the SQL server version if that is the db type we're using
/// </summary>
private void DetermineSqlServerVersion()
{
var sqlServerSyntax = SqlSyntax as SqlServerSyntaxProvider;
if (sqlServerSyntax != null)
{
//this will not execute now, it is lazy so will only execute when we need to actually know
// the sql server version.
sqlServerSyntax.VersionName = new Lazy<SqlServerVersionName>(() =>
{
try
{
var database = this._factory.CreateDatabase();
var version = database.ExecuteScalar<string>("SELECT SERVERPROPERTY('productversion')");
var firstPart = version.Split('.')[0];
switch (firstPart)
{
case "11":
return SqlServerVersionName.V2012;
case "10":
return SqlServerVersionName.V2008;
case "9":
return SqlServerVersionName.V2005;
case "8":
return SqlServerVersionName.V2000;
case "7":
return SqlServerVersionName.V7;
default:
return SqlServerVersionName.Other;
}
}
catch (Exception)
{
return SqlServerVersionName.Invalid;
}
});
}
}
internal DatabaseSchemaResult ValidateDatabaseSchema()
{
if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName)))
@@ -531,7 +517,7 @@ namespace Umbraco.Core
{
if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted
&& ProviderName == Constants.DatabaseProviders.MySql)
&& ProviderName == "MySql.Data.MySqlClient")
{
throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration");
}
@@ -544,7 +530,7 @@ namespace Umbraco.Core
}
internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext)
{
{
try
{
var readyForInstall = CheckReadyForInstall();
@@ -562,7 +548,7 @@ namespace Umbraco.Core
// If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks
// for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive.
/*
/*
var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
if (supportsCaseInsensitiveQueries == false)
{
@@ -580,9 +566,9 @@ namespace Umbraco.Core
message = GetResultMessageForMySql();
var schemaResult = ValidateDatabaseSchema();
var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
//If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing
if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0)))
{
@@ -601,9 +587,9 @@ namespace Umbraco.Core
message = "<p>Upgrading database, this may take some time...</p>";
return new Result
{
RequiresUpgrade = true,
Message = message,
Success = true,
RequiresUpgrade = true,
Message = message,
Success = true,
Percentage = "30"
};
}
@@ -631,7 +617,7 @@ namespace Umbraco.Core
_logger.Info<DatabaseContext>("Database upgrade started");
var database = new UmbracoDatabase(_connectionString, ProviderName, _logger);
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
var message = GetResultMessageForMySql();
@@ -639,32 +625,37 @@ namespace Umbraco.Core
var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion());
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
var installedMigrationVersion = new SemVersion(0);
//we cannot check the migrations table if it doesn't exist, this will occur when upgrading to 7.3
if (schemaResult.ValidTables.Any(x => x.InvariantEquals("umbracoMigration")))
{
installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
}
var targetVersion = UmbracoVersion.Current;
//In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
// migrations are run for the target version
if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0)))
{
//set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute
installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision());
}
//Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum
// version detected between the schema installed and the migrations listed in the migration table.
// version detected between the schema installed and the migrations listed in the migration table.
// If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what
// is declared in the web.config.
var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus)
//Take the minimum version between the detected schema version and the installed migration version
? new[] {installedSchemaVersion, installedMigrationVersion}.Min()
//Take the minimum version between the installed migration version and the version specified in the config
: new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min();
//Ok, another edge case here. If the current version is a pre-release,
// then we want to ensure all migrations for the current release are executed.
//Ok, another edge case here. If the current version is a pre-release,
// then we want to ensure all migrations for the current release are executed.
if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false)
{
currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision());
@@ -740,7 +731,7 @@ namespace Umbraco.Core
private Attempt<Result> CheckReadyForInstall()
{
if (SystemUtilities.GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted
&& ProviderName == Constants.DatabaseProviders.MySql)
&& ProviderName == "MySql.Data.MySqlClient")
{
throw new InvalidOperationException("Cannot use MySql in Medium Trust configuration");
}
@@ -789,7 +780,7 @@ namespace Umbraco.Core
{
var dbIsSqlCe = false;
if (databaseSettings != null && databaseSettings.ProviderName != null)
dbIsSqlCe = databaseSettings.ProviderName == Constants.DatabaseProviders.SqlCe;
dbIsSqlCe = databaseSettings.ProviderName == "System.Data.SqlServerCe.4.0";
var sqlCeDatabaseExists = false;
if (dbIsSqlCe)
{
@@ -799,7 +790,7 @@ namespace Umbraco.Core
{
var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
var filePath = datasource.Replace("Data Source=", string.Empty);
sqlCeDatabaseExists = File.Exists(filePath);
sqlCeDatabaseExists = File.Exists(filePath);
}
}
@@ -813,28 +804,5 @@ namespace Umbraco.Core
return true;
}
private class UsingDatabase : IDisposable
{
private readonly UmbracoDatabase _orig;
private readonly UmbracoDatabase _temp;
public UsingDatabase(UmbracoDatabase orig, UmbracoDatabase temp)
{
_orig = orig;
_temp = temp;
}
public void Dispose()
{
if (_temp != null)
{
_temp.Dispose();
if (_orig != null)
DefaultDatabaseFactory.AttachAmbientDatabase(_orig);
}
GC.SuppressFinalize(this);
}
}
}
}
-23
View File
@@ -1,23 +0,0 @@
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods for System.Decimal.
/// </summary>
/// <remarks>See System.Decimal on MSDN and also
/// http://stackoverflow.com/questions/4298719/parse-decimal-and-filter-extra-0-on-the-right/4298787#4298787.
/// </remarks>
public static class DecimalExtensions
{
/// <summary>
/// Gets the normalized value.
/// </summary>
/// <param name="value">The value to normalize.</param>
/// <returns>The normalized value.</returns>
/// <remarks>Normalizing changes the scaling factor and removes trailing zeroes,
/// so 1.2500m comes out as 1.25m.</remarks>
public static decimal Normalize(this decimal value)
{
return value / 1.000000000000000000000000000000000m;
}
}
}
+1 -32
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Umbraco.Core.Events
{
@@ -48,35 +47,5 @@ namespace Umbraco.Core.Events
if (eventHandler != null)
eventHandler(sender, args);
}
// moves the last handler that was added to an instance event, to first position
public static void PromoteLastHandler(object sender, string eventName)
{
var fieldInfo = sender.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
PromoteLastHandler(sender, fieldInfo);
}
// moves the last handler that was added to a static event, to first position
public static void PromoteLastHandler<TSender>(string eventName)
{
var fieldInfo = typeof(TSender).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic);
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
PromoteLastHandler(null, fieldInfo);
}
private static void PromoteLastHandler(object sender, FieldInfo fieldInfo)
{
var d = fieldInfo.GetValue(sender) as Delegate;
if (d == null) return;
var l = d.GetInvocationList();
var x = l[l.Length - 1];
for (var i = l.Length - 1; i > 0; i--)
l[i] = l[i - 1];
l[0] = x;
fieldInfo.SetValue(sender, Delegate.Combine(l));
}
}
}
}
@@ -128,14 +128,8 @@ namespace Umbraco.Core.Events
get { return TargetSemVersion.GetVersion(); }
}
/// <summary>
/// Gets the origin version of the migration, i.e. the one that is currently installed.
/// </summary>
public SemVersion ConfiguredSemVersion { get; private set; }
/// <summary>
/// Gets the target version of the migration.
/// </summary>
public SemVersion TargetSemVersion { get; private set; }
public string ProductName { get; private set; }
-9
View File
@@ -1,9 +0,0 @@
using System;
namespace Umbraco.Core
{
public interface ICompletable : IDisposable
{
void Complete();
}
}
+5 -7
View File
@@ -37,17 +37,15 @@ namespace Umbraco.Core.IO
throw new ArgumentException("Retries must be greater than zero");
}
// GetSize has been added to IFileSystem2 but not IFileSystem
// this is implementing GetSize for IFileSystem, the old way
public static long GetSize(this IFileSystem fs, string path)
{
// if we reach this point, fs is *not* IFileSystem2
// so it's not FileSystemWrapper nor shadow nor anything we know
// so... fall back to the old & inefficient method
using (var file = fs.OpenFile(path))
{
return file.Length;
using (var sr = new StreamReader(file))
{
var str = sr.ReadToEnd();
return str.Length;
}
}
}
+61 -193
View File
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;
using Umbraco.Core.CodeAnnotations;
using Umbraco.Core.Configuration;
namespace Umbraco.Core.IO
@@ -11,27 +13,8 @@ namespace Umbraco.Core.IO
public class FileSystemProviderManager
{
private readonly FileSystemProvidersSection _config;
private readonly WeakSet<ShadowWrapper> _wrappers = new WeakSet<ShadowWrapper>();
// actual well-known filesystems returned by properties
private readonly IFileSystem2 _macroPartialFileSystem;
private readonly IFileSystem2 _partialViewsFileSystem;
private readonly IFileSystem2 _stylesheetsFileSystem;
private readonly IFileSystem2 _scriptsFileSystem;
private readonly IFileSystem2 _xsltFileSystem;
private readonly IFileSystem2 _masterPagesFileSystem;
private readonly IFileSystem2 _mvcViewsFileSystem;
// when shadowing is enabled, above filesystems, as wrappers
private readonly ShadowWrapper _macroPartialFileSystemWrapper;
private readonly ShadowWrapper _partialViewsFileSystemWrapper;
private readonly ShadowWrapper _stylesheetsFileSystemWrapper;
private readonly ShadowWrapper _scriptsFileSystemWrapper;
private readonly ShadowWrapper _xsltFileSystemWrapper;
private readonly ShadowWrapper _masterPagesFileSystemWrapper;
private readonly ShadowWrapper _mvcViewsFileSystemWrapper;
#region Singleton & Constructor
#region Singleton
private static readonly FileSystemProviderManager Instance = new FileSystemProviderManager();
@@ -40,224 +23,109 @@ namespace Umbraco.Core.IO
get { return Instance; }
}
#endregion
#region Constructors
internal FileSystemProviderManager()
{
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
_macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials);
_partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews);
_stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css);
_scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
_xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials");
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials");
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css");
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts");
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt");
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages");
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views");
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
_config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
}
#endregion
#region Well-Known FileSystems
public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem; } }
public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem; } }
public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem; } }
public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem; } }
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
public IFileSystem2 MasterPagesFileSystem { get { return _masterPagesFileSystem; } }
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } }
public MediaFileSystem MediaFileSystem { get; private set; }
#endregion
#region Providers
/// <summary>
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
/// </summary>
private class ProviderConstructionInfo
/// <summary>
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
/// </summary>
private class ProviderConstructionInfo
{
public object[] Parameters { get; set; }
public ConstructorInfo Constructor { get; set; }
//public string ProviderAlias { get; set; }
public string ProviderAlias { get; set; }
}
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
private readonly ConcurrentDictionary<Type, string> _wrappedProviderLookup = new ConcurrentDictionary<Type, string>();
/// <summary>
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
/// Returns the underlying (non-typed) file system provider for the alias specified
/// </summary>
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
/// <remarks>This method should not be used directly, used <see cref="GetFileSystemProvider{TFileSystem}"/> instead.</remarks>
/// <param name="alias"></param>
/// <returns></returns>
/// <remarks>
/// It is recommended to use the typed GetFileSystemProvider method instead to get a strongly typed provider instance.
/// </remarks>
public IFileSystem GetUnderlyingFileSystemProvider(string alias)
{
// either get the constructor info from cache or create it and add to cache
//either get the constructor info from cache or create it and add to cache
var ctorInfo = _providerLookup.GetOrAdd(alias, s =>
{
// get config
var providerConfig = _config.Providers[s];
if (providerConfig == null)
throw new ArgumentException(string.Format("No provider found with alias {0}.", s));
throw new ArgumentException(string.Format("No provider found with the alias '{0}'", s));
// get the filesystem type
var providerType = Type.GetType(providerConfig.Type);
if (providerType == null)
throw new InvalidOperationException(string.Format("Could not find type {0}.", providerConfig.Type));
throw new InvalidOperationException(string.Format("Could not find type '{0}'", providerConfig.Type));
// ensure it implements IFileSystem
if (providerType.IsAssignableFrom(typeof (IFileSystem)))
throw new InvalidOperationException(string.Format("Type {0} does not implement IFileSystem.", providerType.FullName));
throw new InvalidOperationException(string.Format("The type '{0}' does not implement IFileSystem", providerConfig.Type));
// find a ctor matching the config parameters
var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0;
var constructor = providerType.GetConstructors().SingleOrDefault(x
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
var constructor = providerType.GetConstructors()
.SingleOrDefault(x => x.GetParameters().Count() == paramCount
&& x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
if (constructor == null)
throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount));
throw new InvalidOperationException(string.Format("Could not find constructor for type '{0}' which accepts {1} parameters", providerConfig.Type, paramCount));
var parameters = new object[paramCount];
if (providerConfig.Parameters != null) // keeps ReSharper happy
for (var i = 0; i < paramCount; i++)
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
for (var i = 0; i < paramCount; i++)
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
return new ProviderConstructionInfo
//return the new constructor info class to cache so we don't have to do this again.
return new ProviderConstructionInfo()
{
Constructor = constructor,
Parameters = parameters,
//ProviderAlias = s
ProviderAlias = s
};
});
// create the fs and return
return (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
}
/// <summary>
/// Gets a strongly-typed filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <returns>A strongly-typed filesystem of the specified type.</returns>
public TFileSystem GetFileSystemProvider<TFileSystem>()
where TFileSystem : FileSystemWrapper
{
// deal with known types - avoid infinite loops!
if (typeof(TFileSystem) == typeof(MediaFileSystem) && MediaFileSystem != null)
return MediaFileSystem as TFileSystem; // else create and return
// get/cache the alias for the filesystem type
var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType =>
{
// validate the ctor
var constructor = fsType.GetConstructors().SingleOrDefault(x
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
if (constructor == null)
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
// find the attribute and get the alias
var attr = (FileSystemProviderAttribute) fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault();
if (attr == null)
throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute.");
return attr.Alias;
});
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
// so we are double-wrapping here
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
var innerFs = GetUnderlyingFileSystemProvider(alias);
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias);
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs);
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
var fs = (IFileSystem)ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
return fs;
}
#endregion
#region Shadow
// note
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
// global shadow for the entire application, so great care should be taken to ensure that the
// application is *not* doing anything else when using a shadow.
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
// be created directly (via ctor) or via GetFileSystemProvider<T> is *not* shadowed.
// shadow must be enabled in an app event handler before anything else ie before any filesystem
// is actually created and used - after, it is too late - enabling shadow has a neglictible perfs
// impact.
// NO! by the time an app event handler is instanciated it is already too late, see note in ctor.
//internal void EnableShadow()
//{
// if (_mvcViewsFileSystem != null) // test one of the fs...
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
// _shadowEnabled = true;
//}
public ICompletable Shadow(Guid id)
/// <summary>
/// Returns the strongly typed file system provider
/// </summary>
/// <typeparam name="TProviderTypeFilter"></typeparam>
/// <returns></returns>
public TProviderTypeFilter GetFileSystemProvider<TProviderTypeFilter>()
where TProviderTypeFilter : FileSystemWrapper
{
var typed = _wrappers.ToArray();
var wrappers = new ShadowWrapper[typed.Length + 7];
var i = 0;
while (i < typed.Length) wrappers[i] = typed[i++];
wrappers[i++] = _macroPartialFileSystemWrapper;
wrappers[i++] = _partialViewsFileSystemWrapper;
wrappers[i++] = _stylesheetsFileSystemWrapper;
wrappers[i++] = _scriptsFileSystemWrapper;
wrappers[i++] = _xsltFileSystemWrapper;
wrappers[i++] = _masterPagesFileSystemWrapper;
wrappers[i] = _mvcViewsFileSystemWrapper;
//get the alias for the type from cache or look it up and add it to the cache, then we don't have to reflect each time
var alias = _wrappedProviderLookup.GetOrAdd(typeof (TProviderTypeFilter), fsType =>
{
//validate the ctor
var constructor = fsType.GetConstructors()
.SingleOrDefault(x =>
x.GetParameters().Count() == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
if (constructor == null)
throw new InvalidOperationException("The type of " + fsType + " must inherit from FileSystemWrapper and must have a constructor that accepts one parameter of type " + typeof(IFileSystem));
return ShadowFileSystemsScope.CreateScope(id, wrappers);
}
var attr =
(FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).
SingleOrDefault();
#endregion
if (attr == null)
throw new InvalidOperationException(string.Format("The provider type filter '{0}' is missing the required FileSystemProviderAttribute", typeof(FileSystemProviderAttribute).FullName));
private class WeakSet<T>
where T : class
{
private readonly HashSet<WeakReference<T>> _set = new HashSet<WeakReference<T>>();
public void Add(T item)
{
lock (_set)
{
_set.Add(new WeakReference<T>(item));
CollectLocked();
}
}
public T[] ToArray()
{
lock (_set)
{
CollectLocked();
return _set.Select(x =>
{
T target;
return x.TryGetTarget(out target) ? target : null;
}).WhereNotNull().ToArray();
}
}
private void CollectLocked()
{
_set.RemoveWhere(x =>
{
T target;
return x.TryGetTarget(out target) == false;
});
}
return attr.Alias;
});
var innerFs = GetUnderlyingFileSystemProvider(alias);
var outputFs = Activator.CreateInstance(typeof (TProviderTypeFilter), innerFs);
return (TProviderTypeFilter)outputFs;
}
}
}
+24 -31
View File
@@ -14,100 +14,93 @@ namespace Umbraco.Core.IO
///
/// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor.
/// </remarks>
public abstract class FileSystemWrapper : IFileSystem2
public abstract class FileSystemWrapper : IFileSystem
{
protected FileSystemWrapper(IFileSystem wrapped)
private readonly IFileSystem _wrapped;
protected FileSystemWrapper(IFileSystem wrapped)
{
Wrapped = wrapped;
_wrapped = wrapped;
}
internal IFileSystem Wrapped { get; set; }
public IEnumerable<string> GetDirectories(string path)
public IEnumerable<string> GetDirectories(string path)
{
return Wrapped.GetDirectories(path);
return _wrapped.GetDirectories(path);
}
public void DeleteDirectory(string path)
{
Wrapped.DeleteDirectory(path);
_wrapped.DeleteDirectory(path);
}
public void DeleteDirectory(string path, bool recursive)
{
Wrapped.DeleteDirectory(path, recursive);
_wrapped.DeleteDirectory(path, recursive);
}
public bool DirectoryExists(string path)
{
return Wrapped.DirectoryExists(path);
return _wrapped.DirectoryExists(path);
}
public void AddFile(string path, Stream stream)
{
Wrapped.AddFile(path, stream);
_wrapped.AddFile(path, stream);
}
public void AddFile(string path, Stream stream, bool overrideExisting)
public void AddFile(string path, Stream stream, bool overrideIfExists)
{
Wrapped.AddFile(path, stream, overrideExisting);
_wrapped.AddFile(path, stream, overrideIfExists);
}
public IEnumerable<string> GetFiles(string path)
{
return Wrapped.GetFiles(path);
return _wrapped.GetFiles(path);
}
public IEnumerable<string> GetFiles(string path, string filter)
{
return Wrapped.GetFiles(path, filter);
return _wrapped.GetFiles(path, filter);
}
public Stream OpenFile(string path)
{
return Wrapped.OpenFile(path);
return _wrapped.OpenFile(path);
}
public void DeleteFile(string path)
{
Wrapped.DeleteFile(path);
_wrapped.DeleteFile(path);
}
public bool FileExists(string path)
{
return Wrapped.FileExists(path);
return _wrapped.FileExists(path);
}
public string GetRelativePath(string fullPathOrUrl)
{
return Wrapped.GetRelativePath(fullPathOrUrl);
return _wrapped.GetRelativePath(fullPathOrUrl);
}
public string GetFullPath(string path)
{
return Wrapped.GetFullPath(path);
return _wrapped.GetFullPath(path);
}
public string GetUrl(string path)
{
return Wrapped.GetUrl(path);
return _wrapped.GetUrl(path);
}
public DateTimeOffset GetLastModified(string path)
{
return Wrapped.GetLastModified(path);
return _wrapped.GetLastModified(path);
}
public DateTimeOffset GetCreated(string path)
{
return Wrapped.GetCreated(path);
return _wrapped.GetCreated(path);
}
// explicitely implementing - not breaking
long IFileSystem2.GetSize(string path)
{
var wrapped2 = Wrapped as IFileSystem2;
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
}
}
}
}
+5 -14
View File
@@ -4,6 +4,9 @@ using System.IO;
namespace Umbraco.Core.IO
{
//TODO: There is no way to create a directory here without creating a file in a directory and then deleting it
//TODO: Should probably implement a rename?
public interface IFileSystem
{
IEnumerable<string> GetDirectories(string path);
@@ -13,7 +16,7 @@ namespace Umbraco.Core.IO
void DeleteDirectory(string path, bool recursive);
bool DirectoryExists(string path);
void AddFile(string path, Stream stream);
void AddFile(string path, Stream stream, bool overrideIfExists);
@@ -28,6 +31,7 @@ namespace Umbraco.Core.IO
bool FileExists(string path);
string GetRelativePath(string fullPathOrUrl);
string GetFullPath(string path);
@@ -38,17 +42,4 @@ namespace Umbraco.Core.IO
DateTimeOffset GetCreated(string path);
}
// this should be part of IFileSystem but we don't want to change the interface
public interface IFileSystem2 : IFileSystem
{
long GetSize(string path);
// TODO: implement these
//
//void CreateDirectory(string path);
//
//// move or rename, directory or file
//void Move(string source, string target);
}
}
+37 -649
View File
@@ -1,18 +1,9 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
using Umbraco.Core.Media.Exif;
using Umbraco.Core.Models;
namespace Umbraco.Core.IO
{
@@ -23,662 +14,59 @@ namespace Umbraco.Core.IO
public class MediaFileSystem : FileSystemWrapper
{
private readonly IContentSection _contentConfig;
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
private readonly object _folderCounterLock = new object();
private long _folderCounter;
private bool _folderCounterInitialized;
public MediaFileSystem(IFileSystem wrapped)
: this(wrapped, UmbracoConfig.For.UmbracoSettings().Content)
{
}
private static readonly Dictionary<int, string> DefaultSizes = new Dictionary<int, string>
{
{ 100, "thumb" },
{ 500, "big-thumb" }
};
public MediaFileSystem(IFileSystem wrapped)
: this(wrapped, UmbracoConfig.For.UmbracoSettings().Content, ApplicationContext.Current.ProfilingLogger.Logger)
{ }
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger)
: base(wrapped)
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig) : base(wrapped)
{
_contentConfig = contentConfig;
_uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig);
}
internal UploadAutoFillProperties UploadAutoFillProperties { get { return _uploadAutoFillProperties; } }
// note - this is currently experimental / being developed
//public static bool UseTheNewMediaPathScheme { get; set; }
public const bool UseTheNewMediaPathScheme = false;
// none of the methods below are used in Core anymore
[Obsolete("This low-level method should NOT exist.")]
public string GetRelativePath(int propertyId, string fileName)
public string GetRelativePath(int propertyId, string fileName)
{
var sep = _contentConfig.UploadAllowDirectories
var seperator = _contentConfig.UploadAllowDirectories
? Path.DirectorySeparatorChar
: '-';
return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName;
return propertyId.ToString(CultureInfo.InvariantCulture) + seperator + fileName;
}
[Obsolete("This low-level method should NOT exist.", false)]
public string GetRelativePath(string subfolder, string fileName)
{
var sep = _contentConfig.UploadAllowDirectories
var seperator = _contentConfig.UploadAllowDirectories
? Path.DirectorySeparatorChar
: '-';
return subfolder + sep + fileName;
return subfolder + seperator + fileName;
}
#region Media Path
/// <summary>
/// Gets the file path of a media file.
/// </summary>
/// <param name="filename">The file name.</param>
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
/// <param name="puid">The unique identifier of the property type owning the file.</param>
/// <returns>The filesystem-relative path to the media file.</returns>
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
public string GetMediaPath(string filename, Guid cuid, Guid puid)
{
filename = Path.GetFileName(filename);
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename");
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
string folder;
if (UseTheNewMediaPathScheme == false)
{
// old scheme: filepath is "<int>/<filename>" OR "<int>-<filename>"
// default media filesystem maps to "~/media/<filepath>"
folder = GetNextFolder();
}
else
{
// new scheme: path is "<cuid>-<puid>/<filename>" OR "<cuid>-<puid>-<filename>"
// default media filesystem maps to "~/media/<filepath>"
// assumes that cuid and puid keys can be trusted - and that a single property type
// for a single content cannot store two different files with the same name
folder = Combine(cuid, puid).ToHexString(/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
}
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
? Path.Combine(folder, filename)
: folder + "-" + filename;
return filepath;
}
private static byte[] Combine(Guid guid1, Guid guid2)
{
var bytes1 = guid1.ToByteArray();
var bytes2 = guid2.ToByteArray();
var bytes = new byte[bytes1.Length];
for (var i = 0; i < bytes1.Length; i++)
bytes[i] = (byte)(bytes1[i] ^ bytes2[i]);
return bytes;
}
/// <summary>
/// Gets the file path of a media file.
/// </summary>
/// <param name="filename">The file name.</param>
/// <param name="prevpath">A previous file path.</param>
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
/// <param name="puid">The unique identifier of the property type owning the file.</param>
/// <returns>The filesystem-relative path to the media file.</returns>
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid)
{
if (UseTheNewMediaPathScheme || string.IsNullOrWhiteSpace(prevpath))
return GetMediaPath(filename, cuid, puid);
filename = Path.GetFileName(filename);
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename");
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
// old scheme, with a previous path
// prevpath should be "<int>/<filename>" OR "<int>-<filename>"
// and we want to reuse the "<int>" part, so try to find it
var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-";
var pos = prevpath.IndexOf(sep, StringComparison.Ordinal);
var s = pos > 0 ? prevpath.Substring(0, pos) : null;
int ignored;
var folder = (pos > 0 && int.TryParse(s, out ignored)) ? s : GetNextFolder();
// ReSharper disable once AssignNullToNotNullAttribute
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
? Path.Combine(folder, filename)
: folder + "-" + filename;
return filepath;
}
/// <summary>
/// Gets the next media folder in the original number-based scheme.
/// </summary>
/// <returns></returns>
/// <remarks>Should be private, is internal for legacy FileHandlerData which is obsolete.</remarks>
internal string GetNextFolder()
{
EnsureFolderCounterIsInitialized();
return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture);
}
private void EnsureFolderCounterIsInitialized()
{
lock (_folderCounterLock)
{
if (_folderCounterInitialized) return;
_folderCounter = 1000; // seed
var directories = GetDirectories("");
foreach (var directory in directories)
{
long folderNumber;
if (long.TryParse(directory, out folderNumber) && folderNumber > _folderCounter)
_folderCounter = folderNumber;
}
// note: not multi-domains ie LB safe as another domain could create directories
// while we read and parse them - don't fix, move to new scheme eventually
_folderCounterInitialized = true;
}
}
#endregion
#region Associated Media Files
/// <summary>
/// Stores a media file.
/// </summary>
/// <param name="content">The content item owning the media file.</param>
/// <param name="propertyType">The property type owning the media file.</param>
/// <param name="filename">The media file name.</param>
/// <param name="filestream">A stream containing the media bytes.</param>
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
/// <returns>The filesystem-relative filepath to the media file.</returns>
/// <remarks>
/// <para>The file is considered "owned" by the content/propertyType.</para>
/// <para>If an <paramref name="oldpath"/> is provided then that file (and thumbnails) is deleted
/// before the new file is saved, and depending on the media path scheme, the folder
/// may be reused for the new file.</para>
/// </remarks>
public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath)
{
if (content == null) throw new ArgumentNullException("content");
if (propertyType == null) throw new ArgumentNullException("propertyType");
if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Null or empty.", "filename");
if (filestream == null) throw new ArgumentNullException("filestream");
// clear the old file, if any
if (string.IsNullOrWhiteSpace(oldpath) == false)
DeleteFile(oldpath, true);
// get the filepath, store the data
// use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme
var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key);
AddFile(filepath, filestream);
return filepath;
}
/// <summary>
/// Clears a media file.
/// </summary>
/// <param name="filepath">The filesystem-relative path to the media file.</param>
public new void DeleteFile(string filepath)
{
DeleteFile(filepath, true);
}
/// <summary>
/// Copies a media file.
/// </summary>
/// <param name="content">The content item owning the copy of the media file.</param>
/// <param name="propertyType">The property type owning the copy of the media file.</param>
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath)
{
if (content == null) throw new ArgumentNullException("content");
if (propertyType == null) throw new ArgumentNullException("propertyType");
if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentException("Null or empty.", "sourcepath");
// ensure we have a file to copy
if (FileExists(sourcepath) == false) return null;
// get the filepath
var filename = Path.GetFileName(sourcepath);
var filepath = GetMediaPath(filename, content.Key, propertyType.Key);
this.CopyFile(sourcepath, filepath);
CopyThumbnails(sourcepath, filepath);
return filepath;
}
/// <summary>
/// Gets or creates a property for a content item.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="propertyTypeAlias">The property type alias.</param>
/// <returns>The property.</returns>
private static Property GetProperty(IContentBase content, string propertyTypeAlias)
{
var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (property != null) return property;
var propertyType = content.GetContentType().CompositionPropertyTypes
.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (propertyType == null)
throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
property = new Property(propertyType);
content.Properties.Add(property);
return property;
}
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
{
var property = GetProperty(content, propertyTypeAlias);
var svalue = property.Value as string;
var oldpath = svalue == null ? null : GetRelativePath(svalue);
var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath);
property.Value = GetUrl(filepath);
SetUploadFile(content, property, filepath, filestream);
}
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath)
{
var property = GetProperty(content, propertyTypeAlias);
var svalue = property.Value as string;
var oldpath = svalue == null ? null : GetRelativePath(svalue); // FIXME DELETE?
if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath)
DeleteFile(oldpath, true);
property.Value = GetUrl(filepath);
using (var filestream = OpenFile(filepath))
{
SetUploadFile(content, property, filepath, filestream);
}
}
// sets a file for the FileUpload property editor
// ie generates thumbnails and populates autofill properties
private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream)
{
// check if file is an image (and supports resizing and thumbnails etc)
var extension = Path.GetExtension(filepath);
var isImage = IsImageFile(extension);
// specific stuff for images (thumbnails etc)
if (isImage)
{
using (var image = Image.FromStream(filestream))
{
// use one image for all
GenerateThumbnails(image, filepath, property.PropertyType);
_uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, image);
}
}
else
{
// will use filepath for extension, and filestream for length
_uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream);
}
}
#endregion
#region Image
/// <summary>
/// Gets a value indicating whether the file extension corresponds to an image.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <returns>A value indicating whether the file extension corresponds to an image.</returns>
public bool IsImageFile(string extension)
{
if (extension == null) return false;
extension = extension.TrimStart('.');
return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
}
/// <summary>
/// Gets the dimensions of an image.
/// </summary>
/// <param name="stream">A stream containing the image bytes.</param>
/// <returns>The dimension of the image.</returns>
/// <remarks>First try with EXIF as it is faster and does not load the entire image
/// in memory. Fallback to GDI which means loading the image in memory and thus
/// use potentially large amounts of memory.</remarks>
public Size GetDimensions(Stream stream)
{
//Try to load with exif
try
{
var jpgInfo = ImageFile.FromStream(stream);
if (jpgInfo.Format != ImageFileFormat.Unknown
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
{
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
if (height > 0 && width > 0)
{
return new Size(width, height);
}
}
}
catch (Exception)
{
//We will just swallow, just means we can't read exif data, we don't want to log an error either
}
//we have no choice but to try to read in via GDI
using (var image = Image.FromStream(stream))
{
var fileWidth = image.Width;
var fileHeight = image.Height;
return new Size(fileWidth, fileHeight);
}
}
#endregion
#region Manage thumbnails
// note: this does not find 'custom' thumbnails?
// will find _thumb and _big-thumb but NOT _custom?
public IEnumerable<string> GetThumbnails(string path)
{
var parentDirectory = Path.GetDirectoryName(path);
var extension = Path.GetExtension(path);
return GetFiles(parentDirectory)
.Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb"))
.ToList();
}
public void DeleteFile(string path, bool deleteThumbnails)
{
base.DeleteFile(path);
if (deleteThumbnails == false)
return;
DeleteThumbnails(path);
}
public void DeleteThumbnails(string path)
{
GetThumbnails(path)
.ForEach(x => base.DeleteFile(x));
}
public void CopyThumbnails(string sourcePath, string targetPath)
{
var targetPathBase = Path.GetDirectoryName(targetPath) ?? "";
foreach (var sourceThumbPath in GetThumbnails(sourcePath))
{
var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? "";
var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename);
this.CopyFile(sourceThumbPath, targetThumbPath);
}
}
#endregion
#region GenerateThumbnails
public IEnumerable<ResizedImage> GenerateThumbnails(
Image image,
string filepath,
string preValue)
{
if (string.IsNullOrWhiteSpace(preValue))
return GenerateThumbnails(image, filepath);
var additionalSizes = new List<int>();
var sep = preValue.Contains(",") ? "," : ";";
var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries);
foreach (var value in values)
{
int size;
if (int.TryParse(value, out size))
additionalSizes.Add(size);
}
return GenerateThumbnails(image, filepath, additionalSizes);
}
public IEnumerable<ResizedImage> GenerateThumbnails(
Image image,
string filepath,
IEnumerable<int> additionalSizes = null)
{
var w = image.Width;
var h = image.Height;
var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes);
// start with default sizes,
// add additional sizes,
// filter out duplicates,
// filter out those that would be larger that the original image
// and create the thumbnail
return sizes
.Distinct()
.Where(x => w >= x && h >= x)
.Select(x => GenerateResized(image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x))
.ToList(); // now
}
public IEnumerable<ResizedImage> GenerateThumbnails(
Stream filestream,
string filepath,
PropertyType propertyType)
{
// get the original image from the original stream
if (filestream.CanSeek) filestream.Seek(0, 0); // fixme - what if we cannot seek?
using (var image = Image.FromStream(filestream))
{
return GenerateThumbnails(image, filepath, propertyType);
}
}
public IEnumerable<ResizedImage> GenerateThumbnails(
Image image,
string filepath,
PropertyType propertyType)
{
// if the editor is an upload field, check for additional thumbnail sizes
// that can be defined in the prevalue for the property data type. otherwise,
// just use the default sizes.
var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias
? ApplicationContext.Current.Services.DataTypeService
.GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId)
.FirstOrDefault()
: string.Empty;
return GenerateThumbnails(image, filepath, sizes);
}
#endregion
#region GenerateResized - Generate at resized filepath derived from origin filepath
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight)
{
return GenerateResized(originImage, originFilepath, sizeName, maxWidthHeight, -1, -1);
}
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight)
{
return GenerateResized(originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight);
}
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight)
{
if (string.IsNullOrWhiteSpace(sizeName))
sizeName = "UMBRACOSYSTHUMBNAIL";
var extension = Path.GetExtension(originFilepath) ?? string.Empty;
var filebase = originFilepath.TrimEnd(extension);
var resizedFilepath = filebase + "_" + sizeName + extension;
return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight);
}
#endregion
#region GenerateResizedAt - Generate at specified resized filepath
public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight)
{
return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, -1, -1);
}
public ResizedImage GenerateResizedAt(Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath)
{
return GenerateResizedAt(originImage, resizedFilepath, -1, fixedWidth, fixedHeight);
}
public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight)
{
// target dimensions
int width, height;
// if maxWidthHeight then get ratio
if (maxWidthHeight > 0)
{
var fx = (float)originImage.Size.Width / maxWidthHeight;
var fy = (float)originImage.Size.Height / maxWidthHeight;
var f = Math.Max(fx, fy); // fit in thumbnail size
width = (int)Math.Round(originImage.Size.Width / f);
height = (int)Math.Round(originImage.Size.Height / f);
if (width == 0) width = 1;
if (height == 0) height = 1;
}
else if (fixedWidth > 0 && fixedHeight > 0)
{
width = fixedWidth;
height = fixedHeight;
}
else
{
width = height = 1;
}
// create new image with best quality settings
using (var bitmap = new Bitmap(width, height))
using (var graphics = Graphics.FromImage(bitmap))
{
// if the image size is rather large we cannot use the best quality interpolation mode
// because we'll get out of mem exceptions. So we detect how big the image is and use
// the mid quality interpolation mode when the image size exceeds our max limit.
graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000
? InterpolationMode.Bilinear // mid quality
: InterpolationMode.HighQualityBicubic; // best quality
// everything else is best-quality
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
// copy the old image to the new and resize
var rect = new Rectangle(0, 0, width, height);
graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel);
// copy metadata
// fixme - er... no?
// get an encoder - based upon the file type
var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant();
var encoders = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo encoder;
switch (extension)
{
case "png":
encoder = encoders.Single(t => t.MimeType.Equals("image/png"));
break;
case "gif":
encoder = encoders.Single(t => t.MimeType.Equals("image/gif"));
break;
case "tif":
case "tiff":
encoder = encoders.Single(t => t.MimeType.Equals("image/tiff"));
break;
case "bmp":
encoder = encoders.Single(t => t.MimeType.Equals("image/bmp"));
break;
// TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the
// place so left it here, but it needs to not set a codec if it doesn't know which one to pick
// Note: when fixing this: both .jpg and .jpeg should be handled as extensions
default:
encoder = encoders.Single(t => t.MimeType.Equals("image/jpeg"));
break;
}
// set compresion ratio to 90%
var encoderParams = new EncoderParameters();
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
// save the new image
using (var stream = new MemoryStream())
{
bitmap.Save(stream, encoder, encoderParams);
stream.Seek(0, 0);
if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL"))
{
var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString());
AddFile(filepath, stream);
if (extension != "jpg")
{
filepath = filepath.TrimEnd(extension) + "jpg";
stream.Seek(0, 0);
AddFile(filepath, stream);
}
// TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385
stream.Seek(0, 0);
resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height);
}
AddFile(resizedFilepath, stream);
}
return new ResizedImage(resizedFilepath, width, height);
}
}
#endregion
#region Inner classes
public class ResizedImage
{
public ResizedImage()
{ }
public ResizedImage(string filepath, int width, int height)
{
Filepath = filepath;
Width = width;
Height = height;
}
public string Filepath { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
#endregion
}
public IEnumerable<string> GetThumbnails(string path)
{
var parentDirectory = Path.GetDirectoryName(path);
var extension = Path.GetExtension(path);
return GetFiles(parentDirectory)
.Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb"))
.ToList();
}
public void DeleteFile(string path, bool deleteThumbnails)
{
DeleteFile(path);
if (deleteThumbnails == false)
return;
DeleteThumbnails(path);
}
public void DeleteThumbnails(string path)
{
GetThumbnails(path)
.ForEach(DeleteFile);
}
}
}
+56 -146
View File
@@ -6,32 +6,29 @@ using Umbraco.Core.Logging;
namespace Umbraco.Core.IO
{
public class PhysicalFileSystem : IFileSystem2
public class PhysicalFileSystem : IFileSystem
{
// the rooted, filesystem path, using directory separator chars, NOT ending with a separator
// eg "c:" or "c:\path\to\site" or "\\server\path"
private readonly string _rootPath;
// _rootPath, but with separators replaced by forward-slashes
// eg "c:" or "c:/path/to/site" or "//server/path"
// (is used in GetRelativePath)
private readonly string _rootPathFwd;
// the relative url, using url separator chars, NOT ending with a separator
// eg "" or "/Views" or "/Media" or "/<vpath>/Media" in case of a virtual path
// the ??? url, using url separator chars, NOT ending with a separator
// eg "" (?) or "/Scripts" or ???
private readonly string _rootUrl;
// virtualRoot should be "~/path/to/root" eg "~/Views"
// the "~/" is mandatory.
public PhysicalFileSystem(string virtualRoot)
{
if (virtualRoot == null) throw new ArgumentNullException("virtualRoot");
if (virtualRoot.StartsWith("~/") == false)
throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'");
_rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar);
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
_rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/');
_rootPath = IOHelper.MapPath(virtualRoot);
_rootPath = EnsureDirectorySeparatorChar(_rootPath);
_rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar);
_rootUrl = IOHelper.ResolveUrl(virtualRoot);
_rootUrl = EnsureUrlSeparatorChar(_rootUrl);
_rootUrl = _rootUrl.TrimEnd('/');
}
public PhysicalFileSystem(string rootPath, string rootUrl)
@@ -46,24 +43,22 @@ namespace Umbraco.Core.IO
throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'");
// rootPath should be... rooted, as in, it's a root path!
// but the test suite App.config cannot really "root" anything so we'll have to do it here
//var localRoot = AppDomain.CurrentDomain.BaseDirectory;
var localRoot = IOHelper.GetRootDirectorySafe();
if (Path.IsPathRooted(rootPath) == false)
{
// but the test suite App.config cannot really "root" anything so we have to do it here
var localRoot = IOHelper.GetRootDirectorySafe();
rootPath = Path.Combine(localRoot, rootPath);
}
_rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar);
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
_rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/');
rootPath = EnsureDirectorySeparatorChar(rootPath);
rootUrl = EnsureUrlSeparatorChar(rootUrl);
_rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar);
_rootUrl = rootUrl.TrimEnd('/');
}
/// <summary>
/// Gets directories in a directory.
/// </summary>
/// <param name="path">The filesystem-relative path to the directory.</param>
/// <returns>The filesystem-relative path to the directories in the directory.</returns>
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
public IEnumerable<string> GetDirectories(string path)
{
var fullPath = GetFullPath(path);
@@ -85,20 +80,11 @@ namespace Umbraco.Core.IO
return Enumerable.Empty<string>();
}
/// <summary>
/// Deletes a directory.
/// </summary>
/// <param name="path">The filesystem-relative path of the directory.</param>
public void DeleteDirectory(string path)
{
DeleteDirectory(path, false);
}
/// <summary>
/// Deletes a directory.
/// </summary>
/// <param name="path">The filesystem-relative path of the directory.</param>
/// <param name="recursive">A value indicating whether to recursively delete sub-directories.</param>
public void DeleteDirectory(string path, bool recursive)
{
var fullPath = GetFullPath(path);
@@ -115,71 +101,38 @@ namespace Umbraco.Core.IO
}
}
/// <summary>
/// Gets a value indicating whether a directory exists.
/// </summary>
/// <param name="path">The filesystem-relative path of the directory.</param>
/// <returns>A value indicating whether a directory exists.</returns>
public bool DirectoryExists(string path)
{
var fullPath = GetFullPath(path);
return Directory.Exists(fullPath);
}
/// <summary>
/// Saves a file.
/// </summary>
/// <param name="path">The filesystem-relative path of the file.</param>
/// <param name="stream">A stream containing the file data.</param>
/// <remarks>Overrides the existing file, if any.</remarks>
public void AddFile(string path, Stream stream)
{
AddFile(path, stream, true);
}
/// <summary>
/// Saves a file.
/// </summary>
/// <param name="path">The filesystem-relative path of the file.</param>
/// <param name="stream">A stream containing the file data.</param>
/// <param name="overrideExisting">A value indicating whether to override the existing file, if any.</param>
/// <remarks>If a file exists and <paramref name="overrideExisting"/> is false, an exception is thrown.</remarks>
public void AddFile(string path, Stream stream, bool overrideExisting)
public void AddFile(string path, Stream stream, bool overrideIfExists)
{
var fullPath = GetFullPath(path);
var exists = File.Exists(fullPath);
if (exists && overrideExisting == false)
if (exists && overrideIfExists == false)
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
var directory = Path.GetDirectoryName(fullPath);
if (directory == null) throw new InvalidOperationException("Could not get directory.");
Directory.CreateDirectory(directory); // ensure it exists
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists
if (stream.CanSeek) // fixme - what else?
if (stream.CanSeek)
stream.Seek(0, 0);
using (var destination = (Stream) File.Create(fullPath))
using (var destination = (Stream)File.Create(fullPath))
stream.CopyTo(destination);
}
/// <summary>
/// Gets files in a directory.
/// </summary>
/// <param name="path">The filesystem-relative path of the directory.</param>
/// <returns>The filesystem-relative path to the files in the directory.</returns>
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
public IEnumerable<string> GetFiles(string path)
{
return GetFiles(path, "*.*");
}
/// <summary>
/// Gets files in a directory.
/// </summary>
/// <param name="path">The filesystem-relative path of the directory.</param>
/// <param name="filter">A filter.</param>
/// <returns>The filesystem-relative path to the matching files in the directory.</returns>
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
public IEnumerable<string> GetFiles(string path, string filter)
{
var fullPath = GetFullPath(path);
@@ -201,21 +154,12 @@ namespace Umbraco.Core.IO
return Enumerable.Empty<string>();
}
/// <summary>
/// Opens a file.
/// </summary>
/// <param name="path">The filesystem-relative path to the file.</param>
/// <returns></returns>
public Stream OpenFile(string path)
{
var fullPath = GetFullPath(path);
return File.OpenRead(fullPath);
}
/// <summary>
/// Deletes a file.
/// </summary>
/// <param name="path">The filesystem-relative path to the file.</param>
public void DeleteFile(string path)
{
var fullPath = GetFullPath(path);
@@ -232,53 +176,52 @@ namespace Umbraco.Core.IO
}
}
/// <summary>
/// Gets a value indicating whether a file exists.
/// </summary>
/// <param name="path">The filesystem-relative path to the file.</param>
/// <returns>A value indicating whether the file exists.</returns>
public bool FileExists(string path)
{
var fullpath = GetFullPath(path);
return File.Exists(fullpath);
}
// beware, many things depend on how the GetRelative/AbsolutePath methods work!
/// <summary>
/// Gets the filesystem-relative path of a full path or of an url.
/// Gets the relative path.
/// </summary>
/// <param name="fullPathOrUrl">The full path or url.</param>
/// <returns>The path, relative to this filesystem's root.</returns>
/// <remarks>
/// <para>The relative path is relative to this filesystem's root, not starting with any
/// directory separator. All separators are forward-slashes.</para>
/// directory separator. If input was recognized as a url (path), then output uses url (path) separator
/// chars.</para>
/// </remarks>
public string GetRelativePath(string fullPathOrUrl)
{
// test url
var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char
// if it starts with the root url, strip it and trim the starting slash to make it relative
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
if (IOHelper.PathStartsWith(path, _rootUrl, '/'))
return path.Substring(_rootUrl.Length).TrimStart('/');
if (IOHelper.PathStartsWith(path, _rootUrl, '/')) // if it starts with the root url...
return path.Substring(_rootUrl.Length) // strip it
.TrimStart('/'); // it's relative
// if it starts with the root path, strip it and trim the starting slash to make it relative
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
if (IOHelper.PathStartsWith(path, _rootPathFwd, '/'))
return path.Substring(_rootPathFwd.Length).TrimStart('/');
// test path
path = EnsureDirectorySeparatorChar(fullPathOrUrl);
// unchanged - what else?
return path;
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) // if it starts with the root path
return path.Substring(_rootPath.Length) // strip it
.TrimStart(Path.DirectorySeparatorChar); // it's relative
// unchanged - including separators
return fullPathOrUrl;
}
/// <summary>
/// Gets the full path.
/// </summary>
/// <param name="path">The full or filesystem-relative path.</param>
/// <param name="path">The full or relative path.</param>
/// <returns>The full path.</returns>
/// <remarks>
/// <para>On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this
/// filesystem's root) path. All separators are Path.DirectorySeparatorChar.</para>
/// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar.</para>
/// </remarks>
public string GetFullPath(string path)
{
@@ -286,82 +229,49 @@ namespace Umbraco.Core.IO
var opath = path;
path = EnsureDirectorySeparatorChar(path);
// fixme - this part should go!
// not sure what we are doing here - so if input starts with a (back) slash,
// we assume it's not a FS relative path and we try to convert it... but it
// really makes little sense?
if (path.StartsWith(Path.DirectorySeparatorChar.ToString()))
path = GetRelativePath(path);
// if not already rooted, combine with the root path
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false)
path = Path.Combine(_rootPath, path);
// if already a full path, return
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
return path;
// sanitize - GetFullPath will take care of any relative
// else combine and sanitize, ie GetFullPath will take care of any relative
// segments in path, eg '../../foo.tmp' - it may throw a SecurityException
// if the combined path reaches illegal parts of the filesystem
path = Path.GetFullPath(path);
var fpath = Path.Combine(_rootPath, path);
fpath = Path.GetFullPath(fpath);
// at that point, path is within legal parts of the filesystem, ie we have
// permissions to reach that path, but it may nevertheless be outside of
// our root path, due to relative segments, so better check
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
return path;
if (IOHelper.PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar))
return fpath;
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root.");
}
/// <summary>
/// Gets the url.
/// </summary>
/// <param name="path">The filesystem-relative path.</param>
/// <returns>The url.</returns>
/// <remarks>All separators are forward-slashes.</remarks>
public string GetUrl(string path)
{
path = EnsureUrlSeparatorChar(path).Trim('/');
return _rootUrl + "/" + path;
}
/// <summary>
/// Gets the last-modified date of a directory or file.
/// </summary>
/// <param name="path">The filesystem-relative path to the directory or the file.</param>
/// <returns>The last modified date of the directory or the file.</returns>
public DateTimeOffset GetLastModified(string path)
{
var fullpath = GetFullPath(path);
return DirectoryExists(fullpath)
? new DirectoryInfo(fullpath).LastWriteTimeUtc
: new FileInfo(fullpath).LastWriteTimeUtc;
return DirectoryExists(path)
? new DirectoryInfo(GetFullPath(path)).LastWriteTimeUtc
: new FileInfo(GetFullPath(path)).LastWriteTimeUtc;
}
/// <summary>
/// Gets the created date of a directory or file.
/// </summary>
/// <param name="path">The filesystem-relative path to the directory or the file.</param>
/// <returns>The created date of the directory or the file.</returns>
public DateTimeOffset GetCreated(string path)
{
var fullpath = GetFullPath(path);
return DirectoryExists(fullpath)
? Directory.GetCreationTimeUtc(fullpath)
: File.GetCreationTimeUtc(fullpath);
}
/// <summary>
/// Gets the size of a file.
/// </summary>
/// <param name="path">The filesystem-relative path to the file.</param>
/// <returns>The file of the size, in bytes.</returns>
/// <remarks>If the file does not exist, returns -1.</remarks>
public long GetSize(string path)
{
var fullPath = GetFullPath(path);
var file = new FileInfo(fullPath);
return file.Exists ? file.Length : -1;
return DirectoryExists(path)
? Directory.GetCreationTimeUtc(GetFullPath(path))
: File.GetCreationTimeUtc(GetFullPath(path));
}
#region Helper Methods
+20
View File
@@ -0,0 +1,20 @@
namespace Umbraco.Core.IO
{
internal class ResizedImage
{
public ResizedImage()
{
}
public ResizedImage(int width, int height, string fileName)
{
Width = width;
Height = height;
FileName = fileName;
}
public int Width { get; set; }
public int Height { get; set; }
public string FileName { get; set; }
}
}
-286
View File
@@ -1,286 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Umbraco.Core.IO
{
internal class ShadowFileSystem : IFileSystem2
{
private readonly IFileSystem _fs;
private readonly IFileSystem2 _sfs;
public ShadowFileSystem(IFileSystem fs, IFileSystem2 sfs)
{
_fs = fs;
_sfs = sfs;
}
public IFileSystem Inner
{
get { return _fs; }
}
public void Complete()
{
if (_nodes == null) return;
var exceptions = new List<Exception>();
foreach (var kvp in _nodes)
{
if (kvp.Value.IsExist)
{
if (kvp.Value.IsFile)
{
try
{
using (var stream = _sfs.OpenFile(kvp.Key))
_fs.AddFile(kvp.Key, stream, true);
}
catch (Exception e)
{
exceptions.Add(new Exception("Could not save file \"" + kvp.Key + "\".", e));
}
}
}
else
{
try
{
if (kvp.Value.IsDir)
_fs.DeleteDirectory(kvp.Key, true);
else
_fs.DeleteFile(kvp.Key);
}
catch (Exception e)
{
exceptions.Add(new Exception("Could not delete " + (kvp.Value.IsDir ? "directory": "file") + " \"" + kvp.Key + "\".", e));
}
}
}
_nodes.Clear();
if (exceptions.Count == 0) return;
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
}
private Dictionary<string, ShadowNode> _nodes;
private Dictionary<string, ShadowNode> Nodes { get { return _nodes ?? (_nodes = new Dictionary<string, ShadowNode>()); } }
private class ShadowNode
{
public ShadowNode(bool isDelete, bool isdir)
{
IsDelete = isDelete;
IsDir = isdir;
}
public bool IsDelete { get; private set; }
public bool IsDir { get; private set; }
public bool IsExist { get { return IsDelete == false; } }
public bool IsFile { get { return IsDir == false; } }
}
private static string NormPath(string path)
{
return path.ToLowerInvariant().Replace("\\", "/");
}
// values can be "" (root), "foo", "foo/bar"...
private static bool IsChild(string path, string input)
{
if (input.StartsWith(path) == false || input.Length < path.Length + 2)
return false;
if (path.Length > 0 && input[path.Length] != '/') return false;
var pos = input.IndexOf("/", path.Length + 1, StringComparison.OrdinalIgnoreCase);
return pos < 0;
}
private static bool IsDescendant(string path, string input)
{
if (input.StartsWith(path) == false || input.Length < path.Length + 2)
return false;
return path.Length == 0 || input[path.Length] == '/';
}
public IEnumerable<string> GetDirectories(string path)
{
var normPath = NormPath(path);
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
var directories = _fs.GetDirectories(path);
return directories
.Except(shadows.Where(kvp => (kvp.Value.IsDir && kvp.Value.IsDelete) || (kvp.Value.IsFile && kvp.Value.IsExist))
.Select(kvp => kvp.Key))
.Union(shadows.Where(kvp => kvp.Value.IsDir && kvp.Value.IsExist).Select(kvp => kvp.Key))
.Distinct();
}
public void DeleteDirectory(string path)
{
DeleteDirectory(path, false);
}
public void DeleteDirectory(string path, bool recursive)
{
if (DirectoryExists(path) == false) return;
var normPath = NormPath(path);
if (recursive)
{
Nodes[normPath] = new ShadowNode(true, true);
var remove = Nodes.Where(x => IsDescendant(normPath, x.Key)).ToList();
foreach (var kvp in remove) Nodes.Remove(kvp.Key);
Delete(path, true);
}
else
{
if (Nodes.Any(x => IsChild(normPath, x.Key) && x.Value.IsExist) // shadow content
|| _fs.GetDirectories(path).Any() || _fs.GetFiles(path).Any()) // actual content
throw new InvalidOperationException("Directory is not empty.");
Nodes[path] = new ShadowNode(true, true);
var remove = Nodes.Where(x => IsChild(normPath, x.Key)).ToList();
foreach (var kvp in remove) Nodes.Remove(kvp.Key);
Delete(path, false);
}
}
private void Delete(string path, bool recurse)
{
foreach (var file in _fs.GetFiles(path))
{
Nodes[NormPath(file)] = new ShadowNode(true, false);
}
foreach (var dir in _fs.GetDirectories(path))
{
Nodes[NormPath(dir)] = new ShadowNode(true, true);
if (recurse) Delete(dir, true);
}
}
public bool DirectoryExists(string path)
{
ShadowNode sf;
if (Nodes.TryGetValue(NormPath(path), out sf))
return sf.IsDir && sf.IsExist;
return _fs.DirectoryExists(path);
}
public void AddFile(string path, Stream stream)
{
AddFile(path, stream, true);
}
public void AddFile(string path, Stream stream, bool overrideIfExists)
{
ShadowNode sf;
var normPath = NormPath(path);
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
var parts = normPath.Split('/');
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
ShadowNode sd;
if (Nodes.TryGetValue(dirPath, out sd))
{
if (sd.IsFile) throw new InvalidOperationException("Invalid path.");
if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true);
}
else
{
if (_fs.DirectoryExists(dirPath)) continue;
if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path.");
Nodes[dirPath] = new ShadowNode(false, true);
}
}
_sfs.AddFile(path, stream, overrideIfExists);
Nodes[normPath] = new ShadowNode(false, false);
}
public IEnumerable<string> GetFiles(string path)
{
var normPath = NormPath(path);
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
var files = _fs.GetFiles(path);
return files
.Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir)
.Select(kvp => kvp.Key))
.Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist).Select(kvp => kvp.Key))
.Distinct();
}
public IEnumerable<string> GetFiles(string path, string filter)
{
return _fs.GetFiles(path, filter);
}
public Stream OpenFile(string path)
{
ShadowNode sf;
if (Nodes.TryGetValue(NormPath(path), out sf))
return sf.IsDir || sf.IsDelete ? null : _sfs.OpenFile(path);
return _fs.OpenFile(path);
}
public void DeleteFile(string path)
{
if (FileExists(path) == false) return;
Nodes[NormPath(path)] = new ShadowNode(true, false);
}
public bool FileExists(string path)
{
ShadowNode sf;
if (Nodes.TryGetValue(NormPath(path), out sf))
return sf.IsFile && sf.IsExist;
return _fs.FileExists(path);
}
public string GetRelativePath(string fullPathOrUrl)
{
return _fs.GetRelativePath(fullPathOrUrl);
}
public string GetFullPath(string path)
{
return _fs.GetFullPath(path);
}
public string GetUrl(string path)
{
return _fs.GetUrl(path);
}
public DateTimeOffset GetLastModified(string path)
{
ShadowNode sf;
if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetLastModified(path);
if (sf.IsDelete) throw new InvalidOperationException("Invalid path.");
return _sfs.GetLastModified(path);
}
public DateTimeOffset GetCreated(string path)
{
ShadowNode sf;
if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetCreated(path);
if (sf.IsDelete) throw new InvalidOperationException("Invalid path.");
return _sfs.GetCreated(path);
}
public long GetSize(string path)
{
ShadowNode sf;
if (Nodes.TryGetValue(NormPath(path), out sf) == false)
{
// the inner filesystem (_fs) can be IFileSystem2... or just IFileSystem
// figure it out and use the most effective GetSize method
var fs2 = _fs as IFileSystem2;
return fs2 == null ? _fs.GetSize(path) : fs2.GetSize(path);
}
if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path.");
return _sfs.GetSize(path);
}
}
}
@@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using Umbraco.Core.Logging;
namespace Umbraco.Core.IO
{
internal class ShadowFileSystemsScope : ICompletable
{
// note: taking a reference to the _manager instead of using manager.Current
// to avoid using Current everywhere but really, we support only 1 scope at
// a time, not multiple scopes in case of multiple managers (not supported)
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystemsScope";
private static readonly object Locker = new object();
private readonly Guid _id;
private readonly ShadowWrapper[] _wrappers;
static ShadowFileSystemsScope()
{
SafeCallContext.Register(
() =>
{
var scope = CallContext.LogicalGetData(ItemKey);
CallContext.FreeNamedDataSlot(ItemKey);
return scope;
},
o =>
{
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException();
if (o != null) CallContext.LogicalSetData(ItemKey, o);
});
}
private ShadowFileSystemsScope(Guid id, ShadowWrapper[] wrappers)
{
LogHelper.Debug<ShadowFileSystemsScope>("Shadow " + id + ".");
_id = id;
_wrappers = wrappers;
foreach (var wrapper in _wrappers)
wrapper.Shadow(id);
}
// internal for tests + FileSystemProviderManager
// do NOT use otherwise
internal static ShadowFileSystemsScope CreateScope(Guid id, ShadowWrapper[] wrappers)
{
lock (Locker)
{
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
}
return new ShadowFileSystemsScope(id, wrappers);
}
internal static bool InScope
{
get { return NoScope == false; }
}
internal static bool NoScope
{
get { return CallContext.LogicalGetData(ItemKey) == null; }
}
public void Complete()
{
lock (Locker)
{
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (complete).");
var exceptions = new List<Exception>();
foreach (var wrapper in _wrappers)
{
try
{
// this may throw an AggregateException if some of the changes could not be applied
wrapper.UnShadow(true);
}
catch (AggregateException ae)
{
exceptions.Add(ae);
}
}
if (exceptions.Count > 0)
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
// last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can)
CallContext.FreeNamedDataSlot(ItemKey);
}
}
public void Dispose()
{
lock (Locker)
{
if (CallContext.LogicalGetData(ItemKey) == null) return;
try
{
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (abort)");
foreach (var wrapper in _wrappers)
wrapper.UnShadow(false); // should not throw
}
finally
{
// last, & always
CallContext.FreeNamedDataSlot(ItemKey);
}
}
}
}
}
-158
View File
@@ -1,158 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Umbraco.Core.IO
{
internal class ShadowWrapper : IFileSystem2
{
private readonly IFileSystem _innerFileSystem;
private readonly string _shadowPath;
private ShadowFileSystem _shadowFileSystem;
private string _shadowDir;
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath)
{
_innerFileSystem = innerFileSystem;
_shadowPath = shadowPath;
}
internal void Shadow(Guid id)
{
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
// on ShadowFileSystemsScope.None - and if None is false then we should be running
// in a single thread anyways
var virt = "~/App_Data/Shadow/" + id + "/" + _shadowPath;
_shadowDir = IOHelper.MapPath(virt);
Directory.CreateDirectory(_shadowDir);
var tempfs = new PhysicalFileSystem(virt);
_shadowFileSystem = new ShadowFileSystem(_innerFileSystem, tempfs);
}
internal void UnShadow(bool complete)
{
var shadowFileSystem = _shadowFileSystem;
var dir = _shadowDir;
_shadowFileSystem = null;
_shadowDir = null;
try
{
// this may throw an AggregateException if some of the changes could not be applied
if (complete) shadowFileSystem.Complete();
}
finally
{
// in any case, cleanup
try
{
Directory.Delete(dir, true);
dir = dir.Substring(0, dir.Length - _shadowPath.Length - 1);
if (Directory.EnumerateFileSystemEntries(dir).Any() == false)
Directory.Delete(dir, true);
}
catch
{
// ugly, isn't it? but if we cannot cleanup, bah, just leave it there
}
}
}
private IFileSystem FileSystem
{
get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; }
}
public IEnumerable<string> GetDirectories(string path)
{
return FileSystem.GetDirectories(path);
}
public void DeleteDirectory(string path)
{
FileSystem.DeleteDirectory(path);
}
public void DeleteDirectory(string path, bool recursive)
{
FileSystem.DeleteDirectory(path, recursive);
}
public bool DirectoryExists(string path)
{
return FileSystem.DirectoryExists(path);
}
public void AddFile(string path, Stream stream)
{
FileSystem.AddFile(path, stream);
}
public void AddFile(string path, Stream stream, bool overrideExisting)
{
FileSystem.AddFile(path, stream, overrideExisting);
}
public IEnumerable<string> GetFiles(string path)
{
return FileSystem.GetFiles(path);
}
public IEnumerable<string> GetFiles(string path, string filter)
{
return FileSystem.GetFiles(path, filter);
}
public Stream OpenFile(string path)
{
return FileSystem.OpenFile(path);
}
public void DeleteFile(string path)
{
FileSystem.DeleteFile(path);
}
public bool FileExists(string path)
{
return FileSystem.FileExists(path);
}
public string GetRelativePath(string fullPathOrUrl)
{
return FileSystem.GetRelativePath(fullPathOrUrl);
}
public string GetFullPath(string path)
{
return FileSystem.GetFullPath(path);
}
public string GetUrl(string path)
{
return FileSystem.GetUrl(path);
}
public DateTimeOffset GetLastModified(string path)
{
return FileSystem.GetLastModified(path);
}
public DateTimeOffset GetCreated(string path)
{
return FileSystem.GetCreated(path);
}
public long GetSize(string path)
{
var filesystem = FileSystem; // will be either a ShadowFileSystem OR the actual underlying IFileSystem
// and the underlying filesystem can be IFileSystem2... or just IFileSystem
// figure it out and use the most effective GetSize method
var filesystem2 = filesystem as IFileSystem2;
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
}
}
}
+2 -27
View File
@@ -69,16 +69,7 @@ namespace Umbraco.Core.IO
}
}
public static string AppCode
{
get
{
//NOTE: this is not configurable and shouldn't need to be
return "~/App_Code";
}
}
public static string AppPlugins
public static string AppPlugins
{
get
{
@@ -96,23 +87,7 @@ namespace Umbraco.Core.IO
}
}
public static string PartialViews
{
get
{
return MvcViews + "/Partials/";
}
}
public static string MacroPartials
{
get
{
return MvcViews + "/MacroPartials/";
}
}
public static string Media
{
get
+69 -31
View File
@@ -1,7 +1,13 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Media;
@@ -80,15 +86,15 @@ namespace Umbraco.Core.IO
private void Initialize()
{
Filename = _fs.GetFileName(Path);
var ext = _fs.GetExtension(Path);
Extension = string.IsNullOrEmpty(ext) == false
? ext.TrimStart('.').ToLowerInvariant()
Extension = _fs.GetExtension(Path) != null
? _fs.GetExtension(Path).Substring(1).ToLowerInvariant()
: "";
Url = _fs.GetUrl(Path);
Exists = _fs.FileExists(Path);
if (Exists == false)
{
LogHelper.Warn<UmbracoMediaFile>("The media file doesn't exist: " + Path);
}
}
public bool Exists { get; private set; }
@@ -111,15 +117,27 @@ namespace Umbraco.Core.IO
{
get
{
if (_length.HasValue) return _length.Value;
_length = Exists ? _fs.GetSize(Path) : -1;
if (_length == null)
{
if (Exists)
{
_length = _fs.GetSize(Path);
}
else
{
_length = -1;
}
}
return _length.Value;
}
}
public bool SupportsResizing
{
get { return _fs.IsImageFile(Extension); }
get
{
return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(Extension);
}
}
public string GetFriendlyName()
@@ -129,49 +147,67 @@ namespace Umbraco.Core.IO
public Size GetDimensions()
{
if (_size.HasValue) return _size.Value;
if (_fs.FileExists(Path))
if (_size == null)
{
EnsureFileSupportsResizing();
using (var fs = _fs.OpenFile(Path))
if (_fs.FileExists(Path))
{
_size = _fs.GetDimensions(fs);
EnsureFileSupportsResizing();
using (var fs = _fs.OpenFile(Path))
{
_size = ImageHelper.GetDimensions(fs);
}
}
else
{
_size = new Size(-1, -1);
}
}
else
{
_size = new Size(-1, -1);
}
return _size.Value;
}
public string Resize(int width, int height)
{
if (Exists == false) return string.Empty;
if (Exists)
{
EnsureFileSupportsResizing();
EnsureFileSupportsResizing();
var filepath = Resize(width, height, -1, string.Empty);
return _fs.GetUrl(filepath);
var fileNameThumb = DoResize(width, height, -1, string.Empty);
return _fs.GetUrl(fileNameThumb);
}
return string.Empty;
}
public string Resize(int maxWidthHeight, string fileNameAddition)
{
if (Exists == false) return string.Empty;
if (Exists)
{
EnsureFileSupportsResizing();
EnsureFileSupportsResizing();
var filepath = Resize(-1, -1, maxWidthHeight, fileNameAddition);
return _fs.GetUrl(filepath);
var fileNameThumb = DoResize(-1, -1, maxWidthHeight, fileNameAddition);
return _fs.GetUrl(fileNameThumb);
}
return string.Empty;
}
private string Resize(int width, int height, int maxWidthHeight, string sizeName)
private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition)
{
using (var filestream = _fs.OpenFile(Path))
using (var image = Image.FromStream(filestream))
using (var fs = _fs.OpenFile(Path))
{
return _fs.GenerateResized(image, Path, sizeName, maxWidthHeight, width, height).Filepath;
using (var image = Image.FromStream(fs))
{
var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)))
: string.Format("{0}_{1}." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
var thumbnail = maxWidthHeight == -1
? ImageHelper.GenerateThumbnail(image, width, height, fileNameThumb, Extension, _fs)
: ImageHelper.GenerateThumbnail(image, maxWidthHeight, fileNameThumb, Extension, _fs);
return thumbnail.FileName;
}
}
}
@@ -180,5 +216,7 @@ namespace Umbraco.Core.IO
if (SupportsResizing == false)
throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename));
}
}
}
@@ -1,21 +1,20 @@
using System;
using log4net.Appender;
using log4net.Core;
using log4net.Util;
using System;
using System.Runtime.Remoting.Messaging;
namespace Umbraco.Core.Logging
{
/// <summary>
/// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8
/// </summary>
/// <remarks>
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
public abstract class AsyncForwardingAppenderBase : ForwardingAppender
{
#region Private Members
private const FixFlags DefaultFixFlags = FixFlags.Partial;
private FixFlags fixFlags = DefaultFixFlags;
private LoggingEventHelper loggingEventHelper;
private FixFlags _fixFlags = DefaultFixFlags;
private LoggingEventHelper _loggingEventHelper;
#endregion Private Members
@@ -23,25 +22,10 @@ namespace Umbraco.Core.Logging
public FixFlags Fix
{
get { return fixFlags; }
get { return _fixFlags; }
set { SetFixFlags(value); }
}
/// <summary>
/// Returns HttpContext.Current
/// </summary>
protected internal object HttpContext
{
get
{
return CallContext.HostContext;
}
set
{
CallContext.HostContext = value;
}
}
/// <summary>
/// The logger name that will be used for logging internal errors.
/// </summary>
@@ -54,7 +38,7 @@ namespace Umbraco.Core.Logging
public override void ActivateOptions()
{
base.ActivateOptions();
loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags);
_loggingEventHelper = new LoggingEventHelper(InternalLoggerName, DefaultFixFlags);
InitializeAppenders();
}
@@ -68,10 +52,10 @@ namespace Umbraco.Core.Logging
private void SetFixFlags(FixFlags newFixFlags)
{
if (newFixFlags != fixFlags)
if (newFixFlags != _fixFlags)
{
loggingEventHelper.Fix = newFixFlags;
fixFlags = newFixFlags;
_loggingEventHelper.Fix = newFixFlags;
_fixFlags = newFixFlags;
InitializeAppenders();
}
}
@@ -100,7 +84,7 @@ namespace Umbraco.Core.Logging
protected void ForwardInternalError(string message, Exception exception, Type thisType)
{
LogLog.Error(thisType, message, exception);
var loggingEvent = loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception);
var loggingEvent = _loggingEventHelper.CreateLoggingEvent(Level.Error, message, exception);
ForwardLoggingEvent(loggingEvent, thisType);
}
@@ -1,7 +1,6 @@
using log4net.Core;
using log4net.Util;
using System;
using System.ComponentModel;
using System.Runtime.Remoting.Messaging;
using System.Security.Principal;
using System.Threading;
@@ -13,11 +12,7 @@ namespace Umbraco.Core.Logging
/// <summary>
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// which is based on code by Chris Haines http://cjbhaines.wordpress.com/2012/02/13/asynchronous-log4net-appenders/
/// This is an old/deprecated logger and has been superceded by ParallelForwardingAppender which is included in Umbraco and
/// also by AsyncForwardingAppender in the Log4Net.Async library.
/// </summary>
[Obsolete("This is superceded by the ParallelForwardingAppender, this will be removed in v8")]
[EditorBrowsable(EditorBrowsableState.Never)]
public class AsynchronousRollingFileAppender : RollingFileAppender
{
private RingBuffer<LoggingEvent> pendingAppends;
@@ -203,4 +198,79 @@ namespace Umbraco.Core.Logging
}
}
internal interface IQueue<T>
{
void Enqueue(T item);
bool TryDequeue(out T ret);
}
internal class RingBuffer<T> : IQueue<T>
{
private readonly object lockObject = new object();
private readonly T[] buffer;
private readonly int size;
private int readIndex = 0;
private int writeIndex = 0;
private bool bufferFull = false;
public int Size { get { return size; } }
public event Action<object, EventArgs> BufferOverflow;
public RingBuffer(int size)
{
this.size = size;
buffer = new T[size];
}
public void Enqueue(T item)
{
var bufferWasFull = false;
lock (lockObject)
{
buffer[writeIndex] = item;
writeIndex = (++writeIndex) % size;
if (bufferFull)
{
bufferWasFull = true;
readIndex = writeIndex;
}
else if (writeIndex == readIndex)
{
bufferFull = true;
}
}
if (bufferWasFull)
{
if (BufferOverflow != null)
{
BufferOverflow(this, EventArgs.Empty);
}
}
}
public bool TryDequeue(out T ret)
{
if (readIndex == writeIndex && !bufferFull)
{
ret = default(T);
return false;
}
lock (lockObject)
{
if (readIndex == writeIndex && !bufferFull)
{
ret = default(T);
return false;
}
ret = buffer[readIndex];
buffer[readIndex] = default(T);
readIndex = (++readIndex) % size;
bufferFull = false;
return true;
}
}
}
}
@@ -3,11 +3,7 @@ using System.Linq;
namespace Umbraco.Core.Logging
{
/// <summary>
/// Implements <see cref="ILogger"/> on top of System.Diagnostics.Debug.
/// </summary>
/// <remarks>Useful for tests.</remarks>
public class DebugDiagnosticsLogger : ILogger
internal class DebugDiagnosticsLogger : ILogger
{
public void Error(Type callingType, string message, Exception exception)
{
-12
View File
@@ -1,12 +0,0 @@
namespace Umbraco.Core.Logging
{
/// <summary>
/// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8
/// </summary>
/// <typeparam name="T"></typeparam>
internal interface IQueue<T>
{
void Enqueue(T item);
bool TryDequeue(out T ret);
}
}
@@ -1,46 +0,0 @@
namespace Umbraco.Core.Logging
{
using System;
using System.Runtime.CompilerServices;
using ImageProcessor.Common.Exceptions;
/// <summary>
/// A logger for explicitly logging ImageProcessor exceptions.
/// <remarks>
/// Creating this logger is enough for ImageProcessor to find and replace its in-built debug logger
/// without any additional configuration required. This class currently has to be public in order
/// to do so.
/// </remarks>
/// </summary>
public sealed class ImageProcessorLogger : ImageProcessor.Common.Exceptions.ILogger
{
/// <summary>
/// Logs the specified message as an error.
/// </summary>
/// <typeparam name="T">The type calling the logger.</typeparam>
/// <param name="text">The message to log.</param>
/// <param name="callerName">The property or method name calling the log.</param>
/// <param name="lineNumber">The line number where the method is called.</param>
public void Log<T>(string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0)
{
// Using LogHelper since the ImageProcessor logger expects a parameterless constructor.
var message = string.Format("{0} {1} : {2}", callerName, lineNumber, text);
LogHelper.Error<T>(string.Empty, new ImageProcessingException(message));
}
/// <summary>
/// Logs the specified message as an error.
/// </summary>
/// <param name="type">The type calling the logger.</param>
/// <param name="text">The message to log.</param>
/// <param name="callerName">The property or method name calling the log.</param>
/// <param name="lineNumber">The line number where the method is called.</param>
public void Log(Type type, string text, [CallerMemberName] string callerName = null, [CallerLineNumber] int lineNumber = 0)
{
// Using LogHelper since the ImageProcessor logger expects a parameterless constructor.
var message = string.Format("{0} {1} : {2}", callerName, lineNumber, text);
LogHelper.Error(type, string.Empty, new ImageProcessingException(message));
}
}
}
@@ -3,18 +3,15 @@ using log4net.Core;
namespace Umbraco.Core.Logging
{
/// <remarks>
/// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
internal sealed class LoggingEventContext
internal class LoggingEventContext
{
public LoggingEventContext(LoggingEvent loggingEvent, object httpContext)
public LoggingEventContext(LoggingEvent loggingEvent)
{
LoggingEvent = loggingEvent;
HttpContext = httpContext;
}
public LoggingEvent LoggingEvent { get; set; }
public object HttpContext { get; set; }
}
}
@@ -4,9 +4,9 @@ using log4net.Core;
namespace Umbraco.Core.Logging
{
/// <remarks>
/// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
internal sealed class LoggingEventHelper
internal class LoggingEventHelper
{
// needs to be a seperate class so that location is determined correctly by log4net when required
@@ -23,10 +23,8 @@ namespace Umbraco.Core.Logging
public LoggingEvent CreateLoggingEvent(Level level, string message, Exception exception)
{
var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception)
{
Fix = Fix
};
var loggingEvent = new LoggingEvent(HelperType, null, loggerName, level, message, exception);
loggingEvent.Fix = Fix;
return loggingEvent;
}
}
@@ -1,9 +1,9 @@
using log4net.Core;
using log4net.Util;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using log4net.Core;
using log4net.Util;
namespace Umbraco.Core.Logging
{
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Logging
/// An asynchronous appender based on <see cref="BlockingCollection{T}"/>
/// </summary>
/// <remarks>
/// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8
/// Based on https://github.com/cjbhaines/Log4Net.Async
/// </remarks>
public class ParallelForwardingAppender : AsyncForwardingAppenderBase, IDisposable
{
@@ -22,11 +22,11 @@ namespace Umbraco.Core.Logging
private CancellationTokenSource _loggingCancelationTokenSource;
private CancellationToken _loggingCancelationToken;
private Task _loggingTask;
private Double _shutdownFlushTimeout = 2;
private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(2);
private Double _shutdownFlushTimeout = 1;
private TimeSpan _shutdownFlushTimespan = TimeSpan.FromSeconds(1);
private static readonly Type ThisType = typeof(ParallelForwardingAppender);
private volatile bool shutDownRequested;
private int? bufferSize = DefaultBufferSize;
private volatile bool _shutDownRequested;
private int? _bufferSize = DefaultBufferSize;
#endregion Private Members
@@ -37,8 +37,8 @@ namespace Umbraco.Core.Logging
/// </summary>
public override int? BufferSize
{
get { return bufferSize; }
set { bufferSize = value; }
get { return _bufferSize; }
set { _bufferSize = value; }
}
public int BufferEntryCount
@@ -67,12 +67,7 @@ namespace Umbraco.Core.Logging
protected override string InternalLoggerName
{
get
{
{
return "ParallelForwardingAppender";
}
}
get { return "ParallelForwardingAppender"; }
}
#endregion Properties
@@ -88,7 +83,7 @@ namespace Umbraco.Core.Logging
private void StartForwarding()
{
if (shutDownRequested)
if (_shutDownRequested)
{
return;
}
@@ -116,7 +111,7 @@ namespace Umbraco.Core.Logging
private void CompleteSubscriberTask()
{
shutDownRequested = true;
_shutDownRequested = true;
if (_loggingEvents == null || _loggingEvents.IsAddingCompleted)
{
return;
@@ -159,7 +154,7 @@ namespace Umbraco.Core.Logging
loggingEvent.Fix = Fix;
//In the case where blocking on a full collection, and the task is subsequently completed, the cancellation token
//will prevent the entry from attempting to add to the completed collection which would result in an exception.
_loggingEvents.Add(new LoggingEventContext(loggingEvent, HttpContext), _loggingCancelationToken);
_loggingEvents.Add(new LoggingEventContext(loggingEvent), _loggingCancelationToken);
}
protected override void Append(LoggingEvent[] loggingEvents)
@@ -192,7 +187,6 @@ namespace Umbraco.Core.Logging
//This call blocks until an item is available or until adding is completed
foreach (var entry in _loggingEvents.GetConsumingEnumerable(_loggingCancelationToken))
{
HttpContext = entry.HttpContext;
ForwardLoggingEvent(entry.LoggingEvent, ThisType);
}
}
-78
View File
@@ -1,78 +0,0 @@
using System;
namespace Umbraco.Core.Logging
{
/// <summary>
/// Borrowed from https://github.com/cjbhaines/Log4Net.Async - will reference Nuget packages directly in v8
/// </summary>
/// <typeparam name="T"></typeparam>
internal sealed class RingBuffer<T> : IQueue<T>
{
private readonly object lockObject = new object();
private readonly T[] buffer;
private readonly int size;
private int readIndex = 0;
private int writeIndex = 0;
private bool bufferFull = false;
public int Size { get { return size; } }
public event Action<object, EventArgs> BufferOverflow;
public RingBuffer(int size)
{
this.size = size;
buffer = new T[size];
}
public void Enqueue(T item)
{
var bufferWasFull = false;
lock (lockObject)
{
buffer[writeIndex] = item;
writeIndex = (++writeIndex) % size;
if (bufferFull)
{
bufferWasFull = true;
readIndex = writeIndex;
}
else if (writeIndex == readIndex)
{
bufferFull = true;
}
}
if (bufferWasFull)
{
if (BufferOverflow != null)
{
BufferOverflow(this, EventArgs.Empty);
}
}
}
public bool TryDequeue(out T ret)
{
if (readIndex == writeIndex && !bufferFull)
{
ret = default(T);
return false;
}
lock (lockObject)
{
if (readIndex == writeIndex && !bufferFull)
{
ret = default(T);
return false;
}
ret = buffer[readIndex];
buffer[readIndex] = default(T);
readIndex = (++readIndex) % size;
bufferFull = false;
return true;
}
}
}
}
+22 -60
View File
@@ -1,21 +1,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Core
{
/// <summary>
/// Represents the main AppDomain running for a given application.
/// </summary>
/// <remarks>
/// <para>There can be only one "main" AppDomain running for a given application at a time.</para>
/// <para>When an AppDomain starts, it tries to acquire the main domain status.</para>
/// <para>When an AppDomain stops (eg the application is restarting) it should release the main domain status.</para>
/// <para>It is possible to register against the MainDom and be notified when it is released.</para>
/// </remarks>
internal class MainDom : IRegisteredObject
// represents the main domain
class MainDom : IRegisteredObject
{
#region Vars
@@ -28,7 +25,7 @@ namespace Umbraco.Core
private readonly AsyncLock _asyncLock;
private IDisposable _asyncLocker;
// event wait handle used to notify current main domain that it should
// event wait handle used to notify current main domain that it should
// release the lock because a new domain wants to be the main domain
private readonly EventWaitHandle _signal;
@@ -37,26 +34,16 @@ namespace Umbraco.Core
private volatile bool _signaled; // we have been signaled
// actions to run before releasing the main domain
private readonly SortedList<int, Action> _callbacks = new SortedList<int, Action>(new WeightComparer());
private readonly SortedList<int, Action> _callbacks = new SortedList<int, Action>();
private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
private class WeightComparer : IComparer<int>
{
public int Compare(int x, int y)
{
var result = x.CompareTo(y);
// return "equal" as "greater than"
return result == 0 ? 1 : result;
}
}
#endregion
#region Ctor
// initializes a new instance of MainDom
internal MainDom(ILogger logger)
public MainDom(ILogger logger)
{
_logger = logger;
@@ -65,47 +52,22 @@ namespace Umbraco.Core
if (HostingEnvironment.ApplicationID != null)
appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty);
// combining with the physical path because if running on eg IIS Express,
// two sites could have the same appId even though they are different.
//
// now what could still collide is... two sites, running in two different processes
// and having the same appId, and running on the same app physical path
//
// we *cannot* use the process ID here because when an AppPool restarts it is
// a new process for the same application path
var appPath = HostingEnvironment.ApplicationPhysicalPath;
var hash = (appId + ":::" + appPath).ToSHA1();
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK";
_asyncLock = new AsyncLock(lockName);
var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT";
var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT";
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
}
#endregion
/// <summary>
/// Registers a resource that requires the current AppDomain to be the main domain to function.
/// </summary>
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
/// <param name="weight">An optional weight (lower goes first).</param>
/// <returns>A value indicating whether it was possible to register.</returns>
// register a main domain consumer
public bool Register(Action release, int weight = 100)
{
return Register(null, release, weight);
}
/// <summary>
/// Registers a resource that requires the current AppDomain to be the main domain to function.
/// </summary>
/// <param name="install">An action to execute when registering.</param>
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
/// <param name="weight">An optional weight (lower goes first).</param>
/// <returns>A value indicating whether it was possible to register.</returns>
/// <remarks>If registering is successful, then the <paramref name="install"/> action
/// is guaranteed to execute before the AppDomain releases the main domain status.</remarks>
// register a main domain consumer
public bool Register(Action install, Action release, int weight = 100)
{
lock (_locko)
@@ -135,7 +97,7 @@ namespace Umbraco.Core
try
{
_logger.Info<MainDom>("Stopping...");
_logger.Debug<MainDom>("Stopping...");
foreach (var callback in _callbacks.Values)
{
try
@@ -147,7 +109,7 @@ namespace Umbraco.Core
_logger.Error<MainDom>("Error while running callback, remaining callbacks will not run.", e);
throw;
}
}
_logger.Debug<MainDom>("Stopped.");
}
@@ -156,12 +118,12 @@ namespace Umbraco.Core
// in any case...
_isMainDom = false;
_asyncLocker.Dispose();
_logger.Info<MainDom>("Released MainDom.");
_logger.Debug<MainDom>("Released MainDom.");
}
}
// acquires the main domain
internal bool Acquire()
public bool Acquire()
{
lock (_locko) // we don't want the hosting environment to interfere by signaling
{
@@ -169,11 +131,11 @@ namespace Umbraco.Core
// the handler is not installed so that would be the hosting environment
if (_signaled)
{
_logger.Info<MainDom>("Cannot acquire MainDom (signaled).");
_logger.Debug<MainDom>("Cannot acquire MainDom (signaled).");
return false;
}
_logger.Info<MainDom>("Acquiring MainDom...");
_logger.Debug<MainDom>("Acquiring MainDom...");
// signal other instances that we want the lock, then wait one the lock,
// which may timeout, and this is accepted - see comments below
@@ -200,7 +162,7 @@ namespace Umbraco.Core
HostingEnvironment.RegisterObject(this);
_logger.Info<MainDom>("Acquired MainDom.");
_logger.Debug<MainDom>("Acquired MainDom.");
return true;
}
}
@@ -212,7 +174,7 @@ namespace Umbraco.Core
}
// IRegisteredObject
void IRegisteredObject.Stop(bool immediate)
public void Stop(bool immediate)
{
try
{
-21
View File
@@ -1,21 +0,0 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
namespace Umbraco.Core.Media
{
public static class ImageExtensions
{
/// <summary>
/// Gets the MIME type of an image.
/// </summary>
/// <param name="image">The image.</param>
/// <returns>The MIME type of the image.</returns>
public static string GetMimeType(this Image image)
{
var format = image.RawFormat;
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
return codec.MimeType;
}
}
}
+261
View File
@@ -0,0 +1,261 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using Umbraco.Core.IO;
using Umbraco.Core.Media.Exif;
namespace Umbraco.Core.Media
{
/// <summary>
/// A helper class used for imaging
/// </summary>
internal static class ImageHelper
{
/// <summary>
/// Gets the dimensions of an image based on a stream
/// </summary>
/// <param name="imageStream"></param>
/// <returns></returns>
/// <remarks>
/// First try with EXIF, this is because it is insanely faster and doesn't use any memory to read exif data than to load in the entire
/// image via GDI. Otherwise loading an image into GDI consumes a crazy amount of memory on large images.
///
/// Of course EXIF data might not exist in every file and can only exist in JPGs
/// </remarks>
public static Size GetDimensions(Stream imageStream)
{
//Try to load with exif
try
{
var jpgInfo = ImageFile.FromStream(imageStream);
if (jpgInfo.Format != ImageFileFormat.Unknown
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
{
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
if (height > 0 && width > 0)
{
return new Size(width, height);
}
}
}
catch (Exception)
{
//We will just swallow, just means we can't read exif data, we don't want to log an error either
}
//we have no choice but to try to read in via GDI
using (var image = Image.FromStream(imageStream))
{
var fileWidth = image.Width;
var fileHeight = image.Height;
return new Size(fileWidth, fileHeight);
}
}
public static string GetMimeType(this Image image)
{
var format = image.RawFormat;
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
return codec.MimeType;
}
/// <summary>
/// Creates the thumbnails if the image is larger than all of the specified ones.
/// </summary>
/// <param name="fs"></param>
/// <param name="fileName"></param>
/// <param name="extension"></param>
/// <param name="originalImage"></param>
/// <param name="additionalThumbSizes"></param>
/// <returns></returns>
internal static IEnumerable<ResizedImage> GenerateMediaThumbnails(
IFileSystem fs,
string fileName,
string extension,
Image originalImage,
IEnumerable<int> additionalThumbSizes)
{
var result = new List<ResizedImage>();
var allSizesDictionary = new Dictionary<int, string> { { 100, "thumb" }, { 500, "big-thumb" } };
//combine the static dictionary with the additional sizes with only unique values
var allSizes = allSizesDictionary.Select(kv => kv.Key)
.Union(additionalThumbSizes.Where(x => x > 0).Distinct());
var sizesDictionary = allSizes.ToDictionary(s => s, s => allSizesDictionary.ContainsKey(s) ? allSizesDictionary[s] : "");
foreach (var s in sizesDictionary)
{
var size = s.Key;
var name = s.Value;
if (originalImage.Width >= size && originalImage.Height >= size)
{
result.Add(Resize(fs, fileName, extension, size, name, originalImage));
}
}
return result;
}
/// <summary>
/// Performs an image resize
/// </summary>
/// <param name="fileSystem"></param>
/// <param name="path"></param>
/// <param name="extension"></param>
/// <param name="maxWidthHeight"></param>
/// <param name="fileNameAddition"></param>
/// <param name="originalImage"></param>
/// <returns></returns>
private static ResizedImage Resize(IFileSystem fileSystem, string path, string extension, int maxWidthHeight, string fileNameAddition, Image originalImage)
{
var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)))
: string.Format("{0}_{1}." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
var thumb = GenerateThumbnail(
originalImage,
maxWidthHeight,
fileNameThumb,
extension,
fileSystem);
return thumb;
}
internal static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, string thumbnailFileName, string extension, IFileSystem fs)
{
return GenerateThumbnail(image, maxWidthHeight, -1, -1, thumbnailFileName, extension, fs);
}
internal static ResizedImage GenerateThumbnail(Image image, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs)
{
return GenerateThumbnail(image, -1, fixedWidth, fixedHeight, thumbnailFileName, extension, fs);
}
private static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs)
{
// Generate thumbnail
float f = 1;
if (maxWidthHeight >= 0)
{
var fx = (float)image.Size.Width / maxWidthHeight;
var fy = (float)image.Size.Height / maxWidthHeight;
// must fit in thumbnail size
f = Math.Max(fx, fy);
}
//depending on if we are doing fixed width resizing or not.
fixedWidth = (maxWidthHeight > 0) ? image.Width : fixedWidth;
fixedHeight = (maxWidthHeight > 0) ? image.Height : fixedHeight;
var widthTh = (int)Math.Round(fixedWidth / f);
var heightTh = (int)Math.Round(fixedHeight / f);
// fixes for empty width or height
if (widthTh == 0)
widthTh = 1;
if (heightTh == 0)
heightTh = 1;
// Create new image with best quality settings
using (var bp = new Bitmap(widthTh, heightTh))
{
using (var g = Graphics.FromImage(bp))
{
//if the image size is rather large we cannot use the best quality interpolation mode
// because we'll get out of mem exceptions. So we'll detect how big the image is and use
// the mid quality interpolation mode when the image size exceeds our max limit.
if (image.Width > 5000 || image.Height > 5000)
{
//use mid quality
g.InterpolationMode = InterpolationMode.Bilinear;
}
else
{
//use best quality
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
}
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
// Copy the old image to the new and resized
var rect = new Rectangle(0, 0, widthTh, heightTh);
g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
// Copy metadata
var imageEncoders = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo codec;
switch (extension.ToLower())
{
case "png":
codec = imageEncoders.Single(t => t.MimeType.Equals("image/png"));
break;
case "gif":
codec = imageEncoders.Single(t => t.MimeType.Equals("image/gif"));
break;
case "tif":
case "tiff":
codec = imageEncoders.Single(t => t.MimeType.Equals("image/tiff"));
break;
case "bmp":
codec = imageEncoders.Single(t => t.MimeType.Equals("image/bmp"));
break;
// TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the
// place so left it here, but it needs to not set a codec if it doesn't know which one to pick
// Note: when fixing this: both .jpg and .jpeg should be handled as extensions
default:
codec = imageEncoders.Single(t => t.MimeType.Equals("image/jpeg"));
break;
}
// Set compresion ratio to 90%
var ep = new EncoderParameters();
ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
// Save the new image using the dimensions of the image
var predictableThumbnailName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToString(CultureInfo.InvariantCulture));
var predictableThumbnailNameJpg = predictableThumbnailName.Substring(0, predictableThumbnailName.LastIndexOf(".", StringComparison.Ordinal)) + ".jpg";
using (var ms = new MemoryStream())
{
bp.Save(ms, codec, ep);
ms.Seek(0, 0);
fs.AddFile(predictableThumbnailName, ms);
fs.AddFile(predictableThumbnailNameJpg, ms);
}
// TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385
var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh));
using (var ms = new MemoryStream())
{
bp.Save(ms, codec, ep);
ms.Seek(0, 0);
fs.AddFile(newFileName, ms);
}
return new ResizedImage(widthTh, heightTh, newFileName);
}
}
}
}
}
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.IO;
namespace Umbraco.Core.Media
{
/// <summary>
/// Internal singleton to handle the numbering of subfolders within the Media-folder.
/// When this class is initiated it will look for numbered subfolders and select the highest number,
/// which will be the start point for the naming of the next subfolders. If no subfolders exists
/// then the starting point will be 1000, ie. /media/1000/koala.jpg
/// </summary>
internal class MediaSubfolderCounter
{
#region Singleton
private long _numberedFolder = 1000;//Default starting point
private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim();
private static readonly Lazy<MediaSubfolderCounter> Lazy = new Lazy<MediaSubfolderCounter>(() => new MediaSubfolderCounter());
public static MediaSubfolderCounter Current { get { return Lazy.Value; } }
private MediaSubfolderCounter()
{
var folders = new List<long>();
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
var directories = fs.GetDirectories("");
foreach (var directory in directories)
{
long dirNum;
if (long.TryParse(directory, out dirNum))
{
folders.Add(dirNum);
}
}
var last = folders.OrderBy(x => x).LastOrDefault();
if(last != default(long))
_numberedFolder = last;
}
#endregion
/// <summary>
/// Returns an increment of the numbered media subfolders.
/// </summary>
/// <returns>A <see cref="System.Int64"/> value</returns>
public long Increment()
{
using (new ReadLock(ClearLock))
{
_numberedFolder = _numberedFolder + 1;
return _numberedFolder;
}
}
}
}
@@ -1,220 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
namespace Umbraco.Core.Media
{
/// <summary>
/// Provides methods to manage auto-fill properties for upload fields.
/// </summary>
internal class UploadAutoFillProperties
{
private readonly ILogger _logger;
private readonly MediaFileSystem _mediaFileSystem;
private readonly IContentSection _contentSettings;
public UploadAutoFillProperties(MediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSettings)
{
_mediaFileSystem = mediaFileSystem;
_logger = logger;
_contentSettings = contentSettings;
}
/// <summary>
/// Gets the auto-fill configuration for a specified property alias.
/// </summary>
/// <param name="propertyTypeAlias">The property type alias.</param>
/// <returns>The auto-fill configuration for the specified property alias, or null.</returns>
public IImagingAutoFillUploadField GetConfig(string propertyTypeAlias)
{
var autoFillConfigs = _contentSettings.ImageAutoFillProperties;
return autoFillConfigs == null ? null : autoFillConfigs.FirstOrDefault(x => x.Alias == propertyTypeAlias);
}
/// <summary>
/// Resets the auto-fill properties of a content item, for a specified property alias.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="propertyTypeAlias">The property type alias.</param>
public void Reset(IContentBase content, string propertyTypeAlias)
{
if (content == null) throw new ArgumentNullException("content");
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
// get the config, no config = nothing to do
var autoFillConfig = GetConfig(propertyTypeAlias);
if (autoFillConfig == null) return; // nothing
// reset
Reset(content, autoFillConfig);
}
/// <summary>
/// Resets the auto-fill properties of a content item, for a specified auto-fill configuration.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="autoFillConfig">The auto-fill configuration.</param>
public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig)
{
if (content == null) throw new ArgumentNullException("content");
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
ResetProperties(content, autoFillConfig);
}
/// <summary>
/// Populates the auto-fill properties of a content item.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="propertyTypeAlias">The property type alias.</param>
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
public void Populate(IContentBase content, string propertyTypeAlias, string filepath)
{
if (content == null) throw new ArgumentNullException("content");
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
// no property = nothing to do
if (content.Properties.Contains(propertyTypeAlias) == false) return;
// get the config, no config = nothing to do
var autoFillConfig = GetConfig(propertyTypeAlias);
if (autoFillConfig == null) return; // nothing
// populate
Populate(content, autoFillConfig, filepath);
}
/// <summary>
/// Populates the auto-fill properties of a content item.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="propertyTypeAlias">The property type alias.</param>
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
/// <param name="filestream">The stream containing the file data.</param>
/// <param name="image">The file data as an image object.</param>
public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, Image image = null)
{
if (content == null) throw new ArgumentNullException("content");
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
// no property = nothing to do
if (content.Properties.Contains(propertyTypeAlias) == false) return;
// get the config, no config = nothing to do
var autoFillConfig = GetConfig(propertyTypeAlias);
if (autoFillConfig == null) return; // nothing
// populate
Populate(content, autoFillConfig, filepath, filestream, image);
}
/// <summary>
/// Populates the auto-fill properties of a content item, for a specified auto-fill configuration.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="autoFillConfig">The auto-fill configuration.</param>
/// <param name="filepath">The filesystem path to the uploaded file.</param>
/// <remarks>The <paramref name="filepath"/> parameter is the path relative to the filesystem.</remarks>
public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath)
{
if (content == null) throw new ArgumentNullException("content");
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
// no file = reset, file = auto-fill
if (filepath.IsNullOrWhiteSpace())
{
ResetProperties(content, autoFillConfig);
}
else
{
// if anything goes wrong, just reset the properties
try
{
using (var filestream = _mediaFileSystem.OpenFile(filepath))
{
var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null;
SetProperties(content, autoFillConfig, size, filestream.Length, extension);
}
}
catch (Exception ex)
{
_logger.Error(typeof(UploadAutoFillProperties), "Could not populate upload auto-fill properties for file \""
+ filepath + "\".", ex);
ResetProperties(content, autoFillConfig);
}
}
}
/// <summary>
/// Populates the auto-fill properties of a content item.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="autoFillConfig"></param>
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
/// <param name="filestream">The stream containing the file data.</param>
/// <param name="image">The file data as an image object.</param>
public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, Image image = null)
{
if (content == null) throw new ArgumentNullException("content");
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
// no file = reset, file = auto-fill
if (filepath.IsNullOrWhiteSpace() || filestream == null)
{
ResetProperties(content, autoFillConfig);
}
else
{
var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
Size? size;
if (image == null)
size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null;
else
size = new Size(image.Width, image.Height);
SetProperties(content, autoFillConfig, size, filestream.Length, extension);
}
}
private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension)
{
if (content == null) throw new ArgumentNullException("content");
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
if (content.Properties.Contains(autoFillConfig.WidthFieldAlias))
content.Properties[autoFillConfig.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty;
if (content.Properties.Contains(autoFillConfig.HeightFieldAlias))
content.Properties[autoFillConfig.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty;
if (content.Properties.Contains(autoFillConfig.LengthFieldAlias))
content.Properties[autoFillConfig.LengthFieldAlias].Value = length;
if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias))
content.Properties[autoFillConfig.ExtensionFieldAlias].Value = extension;
}
private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig)
{
if (content == null) throw new ArgumentNullException("content");
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
if (content.Properties.Contains(autoFillConfig.WidthFieldAlias))
content.Properties[autoFillConfig.WidthFieldAlias].Value = string.Empty;
if (content.Properties.Contains(autoFillConfig.HeightFieldAlias))
content.Properties[autoFillConfig.HeightFieldAlias].Value = string.Empty;
if (content.Properties.Contains(autoFillConfig.LengthFieldAlias))
content.Properties[autoFillConfig.LengthFieldAlias].Value = string.Empty;
if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias))
content.Properties[autoFillConfig.ExtensionFieldAlias].Value = string.Empty;
}
}
}
+72 -21
View File
@@ -76,19 +76,14 @@ namespace Umbraco.Core.Models
_contentType = contentType;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.Published);
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.Language);
public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ReleaseDate);
public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ExpireDate);
public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo<Content, int>(x => x.WriterId);
public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.NodeName);
public readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.PermissionsChanged);
}
private static readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
private static readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.Published);
private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.Language);
private static readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ReleaseDate);
private static readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ExpireDate);
private static readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo<Content, int>(x => x.WriterId);
private static readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.NodeName);
private static readonly PropertyInfo PermissionsChangedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.PermissionsChanged);
/// <summary>
/// Gets or sets the template used by the Content.
@@ -102,7 +97,14 @@ namespace Umbraco.Core.Models
public virtual ITemplate Template
{
get { return _template; }
set { SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_template = value;
return _template;
}, _template, TemplateSelector);
}
}
/// <summary>
@@ -140,7 +142,14 @@ namespace Umbraco.Core.Models
public bool Published
{
get { return _published; }
internal set { SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); }
internal set
{
SetPropertyValueAndDetectChanges(o =>
{
_published = value;
return _published;
}, _published, PublishedSelector);
}
}
/// <summary>
@@ -151,7 +160,14 @@ namespace Umbraco.Core.Models
public string Language
{
get { return _language; }
set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_language = value;
return _language;
}, _language, LanguageSelector);
}
}
/// <summary>
@@ -161,7 +177,14 @@ namespace Umbraco.Core.Models
public DateTime? ReleaseDate
{
get { return _releaseDate; }
set { SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_releaseDate = value;
return _releaseDate;
}, _releaseDate, ReleaseDateSelector);
}
}
/// <summary>
@@ -171,7 +194,14 @@ namespace Umbraco.Core.Models
public DateTime? ExpireDate
{
get { return _expireDate; }
set { SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_expireDate = value;
return _expireDate;
}, _expireDate, ExpireDateSelector);
}
}
/// <summary>
@@ -181,7 +211,14 @@ namespace Umbraco.Core.Models
public virtual int WriterId
{
get { return _writer; }
set { SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_writer = value;
return _writer;
}, _writer, WriterSelector);
}
}
/// <summary>
@@ -194,7 +231,14 @@ namespace Umbraco.Core.Models
internal string NodeName
{
get { return _nodeName; }
set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_nodeName = value;
return _nodeName;
}, _nodeName, NodeNameSelector);
}
}
/// <summary>
@@ -204,7 +248,14 @@ namespace Umbraco.Core.Models
internal bool PermissionsChanged
{
get { return _permissionsChanged; }
set { SetPropertyValueAndDetectChanges(value, ref _permissionsChanged, Ps.Value.PermissionsChangedSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_permissionsChanged = value;
return _permissionsChanged;
}, _permissionsChanged, PermissionsChangedSelector);
}
}
/// <summary>
+67 -23
View File
@@ -81,24 +81,19 @@ namespace Umbraco.Core.Models
_additionalData = new Dictionary<string, object>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Name);
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ParentId);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.SortOrder);
public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.Level);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Path);
public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.CreatorId);
public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<ContentBase, bool>(x => x.Trashed);
public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ContentTypeId);
public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentBase, PropertyCollection>(x => x.Properties);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Name);
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ParentId);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.SortOrder);
private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.Level);
private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Path);
private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.CreatorId);
private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<ContentBase, bool>(x => x.Trashed);
private static readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ContentTypeId);
private readonly static PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentBase, PropertyCollection>(x => x.Properties);
protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PropertyCollectionSelector);
OnPropertyChanged(PropertyCollectionSelector);
}
/// <summary>
@@ -119,7 +114,7 @@ namespace Umbraco.Core.Models
set
{
_parentId = new Lazy<int>(() => value);
OnPropertyChanged(Ps.Value.ParentIdSelector);
OnPropertyChanged(ParentIdSelector);
}
}
@@ -130,7 +125,14 @@ namespace Umbraco.Core.Models
public virtual string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -140,7 +142,14 @@ namespace Umbraco.Core.Models
public virtual int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
@@ -150,7 +159,14 @@ namespace Umbraco.Core.Models
public virtual int Level
{
get { return _level; }
set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_level = value;
return _level;
}, _level, LevelSelector);
}
}
/// <summary>
@@ -160,7 +176,14 @@ namespace Umbraco.Core.Models
public virtual string Path //Setting this value should be handled by the class not the user
{
get { return _path; }
set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_path = value;
return _path;
}, _path, PathSelector);
}
}
/// <summary>
@@ -170,7 +193,14 @@ namespace Umbraco.Core.Models
public virtual int CreatorId
{
get { return _creatorId; }
set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_creatorId = value;
return _creatorId;
}, _creatorId, CreatorIdSelector);
}
}
/// <summary>
@@ -182,7 +212,14 @@ namespace Umbraco.Core.Models
public virtual bool Trashed //Setting this value should be handled by the class not the user
{
get { return _trashed; }
internal set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); }
internal set
{
SetPropertyValueAndDetectChanges(o =>
{
_trashed = value;
return _trashed;
}, _trashed, TrashedSelector);
}
}
/// <summary>
@@ -207,7 +244,14 @@ namespace Umbraco.Core.Models
}
return _contentTypeId;
}
protected set { SetPropertyValueAndDetectChanges(value, ref _contentTypeId, Ps.Value.DefaultContentTypeIdSelector); }
protected set
{
SetPropertyValueAndDetectChanges(o =>
{
_contentTypeId = value;
return _contentTypeId;
}, _contentTypeId, DefaultContentTypeIdSelector);
}
}
/// <summary>
+128 -80
View File
@@ -1,15 +1,25 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Media;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Strings;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
@@ -319,7 +329,7 @@ namespace Umbraco.Core.Models
{
if (property.Value is string)
{
var value = (string) property.Value;
var value = (string)property.Value;
property.Value = value.ToValidXmlString();
}
}
@@ -434,116 +444,154 @@ namespace Umbraco.Core.Models
}
}
public static IContentTypeComposition GetContentType(this IContentBase contentBase)
{
if (contentBase == null) throw new ArgumentNullException("contentBase");
var content = contentBase as IContent;
if (content != null) return content.ContentType;
var media = contentBase as IMedia;
if (media != null) return media.ContentType;
var member = contentBase as IMember;
if (member != null) return member.ContentType;
throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + ".");
}
#region SetValue for setting file contents
/// <summary>
/// Stores and sets an uploaded HttpPostedFileBase as a property value.
/// Sets and uploads the file from a HttpPostedFileBase object as the property value
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="value">The uploaded <see cref="HttpPostedFileBase"/>.</param>
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
/// <param name="value">The <see cref="HttpPostedFileBase"/> containing the file that will be uploaded</param>
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
{
// ensure we get the filename without the path in IE in intranet mode
// Ensure we get the filename without the path in IE in intranet mode
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
var filename = value.FileName;
var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture);
if (pos > 0)
filename = filename.Substring(pos + 1);
var fileName = value.FileName;
if (fileName.LastIndexOf(@"\") > 0)
fileName = fileName.Substring(fileName.LastIndexOf(@"\") + 1);
// strip any directory info
pos = filename.LastIndexOf(IOHelper.DirSepChar);
if (pos > 0)
filename = filename.Substring(pos + 1);
var name =
IOHelper.SafeFileName(
fileName.Substring(fileName.LastIndexOf(IOHelper.DirSepChar) + 1,
fileName.Length - fileName.LastIndexOf(IOHelper.DirSepChar) - 1)
.ToLower());
// get a safe filename - should this be done by MediaHelper?
filename = IOHelper.SafeFileName(filename);
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower(); // fixme - er... why?
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream);
if (string.IsNullOrEmpty(name) == false)
SetFileOnContent(content, propertyTypeAlias, name, value.InputStream);
}
/// <summary>
/// Stores and sets an uploaded HttpPostedFile as a property value.
/// Sets and uploads the file from a HttpPostedFile object as the property value
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="value">The uploaded <see cref="HttpPostedFile"/>.</param>
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
/// <param name="value">The <see cref="HttpPostedFile"/> containing the file that will be uploaded</param>
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value)
{
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) new HttpPostedFileWrapper(value));
SetValue(content, propertyTypeAlias, (HttpPostedFileBase)new HttpPostedFileWrapper(value));
}
/// <summary>
/// Stores and sets an uploaded HttpPostedFileWrapper as a property value.
/// Sets and uploads the file from a HttpPostedFileWrapper object as the property value
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="value">The uploaded <see cref="HttpPostedFileWrapper"/>.</param>
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
/// <param name="value">The <see cref="HttpPostedFileWrapper"/> containing the file that will be uploaded</param>
[Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")]
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value)
{
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) value);
SetValue(content, propertyTypeAlias, (HttpPostedFileBase)value);
}
/// <summary>
/// Stores and sets a file as a property value.
/// Sets and uploads the file from a <see cref="Stream"/> as the property value
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="filename">The name of the file.</param>
/// <param name="filestream">A stream containing the file data.</param>
/// <remarks>This really is for FileUpload fields only, and should be obsoleted. For anything else,
/// you need to store the file by yourself using Store and then figure out
/// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself.</remarks>
public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
/// <param name="fileName">Name of the file</param>
/// <param name="fileStream"><see cref="Stream"/> to save to disk</param>
public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream)
{
if (filename == null || filestream == null) return;
var name = IOHelper.SafeFileName(fileName);
// get a safe filename - should this be done by MediaHelper?
filename = IOHelper.SafeFileName(filename);
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower(); // fixme - er... why?
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream);
if (string.IsNullOrEmpty(name) == false && fileStream != null)
SetFileOnContent(content, propertyTypeAlias, name, fileStream);
}
/// <summary>
/// Stores a file.
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="filename">The name of the file.</param>
/// <param name="filestream">A stream containing the file data.</param>
/// <param name="filepath">The original file path, if any.</param>
/// <returns>The path to the file, relative to the media filesystem.</returns>
/// <remarks>
/// <para>Does NOT set the property value, so one should probably store the file and then do
/// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath).</para>
/// <para>The original file path is used, in the old media file path scheme, to try and reuse
/// the "folder number" that was assigned to the previous file referenced by the property,
/// if any.</para>
/// </remarks>
public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath)
private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream)
{
var propertyType = content.GetContentType()
.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + ".");
return FileSystemProviderManager.Current.MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath);
var property = content.Properties.FirstOrDefault(x => x.Alias == propertyTypeAlias);
if (property == null)
return;
//TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change FileUploadPropertyValueEditor to do the same!
var numberedFolder = MediaSubfolderCounter.Current.Increment();
var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
? Path.Combine(numberedFolder.ToString(CultureInfo.InvariantCulture), filename)
: numberedFolder + "-" + filename;
var extension = Path.GetExtension(filename).Substring(1).ToLowerInvariant();
//the file size is the length of the stream in bytes
var fileSize = fileStream.Length;
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
fs.AddFile(fileName, fileStream);
//Check if file supports resizing and create thumbnails
var supportsResizing = UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
//the config section used to auto-fill properties
IImagingAutoFillUploadField uploadFieldConfigNode = null;
//Check for auto fill of additional properties
if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties != null)
{
uploadFieldConfigNode = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties
.FirstOrDefault(x => x.Alias == propertyTypeAlias);
}
if (supportsResizing)
{
//get the original image from the original stream
if (fileStream.CanSeek) fileStream.Seek(0, 0);
using (var originalImage = Image.FromStream(fileStream))
{
var additionalSizes = new List<int>();
//Look up Prevalues for this upload datatype - if it is an upload datatype - get additional configured sizes
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
{
//Get Prevalues by the DataType's Id: property.PropertyType.DataTypeId
var values = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId);
var thumbnailSizes = values.FirstOrDefault();
//Additional thumbnails configured as prevalues on the DataType
if (thumbnailSizes != null)
{
foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries))
{
int thumbSize;
if (thumb != "" && int.TryParse(thumb, out thumbSize))
{
additionalSizes.Add(thumbSize);
}
}
}
}
ImageHelper.GenerateMediaThumbnails(fs, fileName, extension, originalImage, additionalSizes);
//while the image is still open, we'll check if we need to auto-populate the image properties
if (uploadFieldConfigNode != null)
{
content.SetValue(uploadFieldConfigNode.WidthFieldAlias, originalImage.Width.ToString(CultureInfo.InvariantCulture));
content.SetValue(uploadFieldConfigNode.HeightFieldAlias, originalImage.Height.ToString(CultureInfo.InvariantCulture));
}
}
}
//if auto-fill is true, then fill the remaining, non-image properties
if (uploadFieldConfigNode != null)
{
content.SetValue(uploadFieldConfigNode.LengthFieldAlias, fileSize.ToString(CultureInfo.InvariantCulture));
content.SetValue(uploadFieldConfigNode.ExtensionFieldAlias, extension);
}
//Set the value of the property to that of the uploaded file's url
property.Value = fs.GetUrl(fileName);
}
#endregion
+15 -9
View File
@@ -48,13 +48,8 @@ namespace Umbraco.Core.Models
_allowedTemplates = new List<ITemplate>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo<ContentType, int>(x => x.DefaultTemplateId);
public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo<ContentType, IEnumerable<ITemplate>>(x => x.AllowedTemplates);
}
private static readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo<ContentType, int>(x => x.DefaultTemplateId);
private static readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo<ContentType, IEnumerable<ITemplate>>(x => x.AllowedTemplates);
/// <summary>
/// Gets or sets the alias of the default Template.
@@ -75,7 +70,14 @@ namespace Umbraco.Core.Models
internal int DefaultTemplateId
{
get { return _defaultTemplate; }
set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_defaultTemplate = value;
return _defaultTemplate;
}, _defaultTemplate, DefaultTemplateSelector);
}
}
/// <summary>
@@ -90,7 +92,11 @@ namespace Umbraco.Core.Models
get { return _allowedTemplates; }
set
{
SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector,
SetPropertyValueAndDetectChanges(o =>
{
_allowedTemplates = value;
return _allowedTemplates;
}, _allowedTemplates, AllowedTemplatesSelector,
//Custom comparer for enumerable
new DelegateEqualityComparer<IEnumerable<ITemplate>>(
(templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
+123 -45
View File
@@ -67,38 +67,33 @@ namespace Umbraco.Core.Models
_additionalData = new Dictionary<string, object>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Name);
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.ParentId);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.SortOrder);
public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.Level);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Path);
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Alias);
public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Description);
public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Icon);
public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Thumbnail);
public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.CreatorId);
public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.AllowedAsRoot);
public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.IsContainer);
public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.Trashed);
public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<ContentTypeSort>>(x => x.AllowedContentTypes);
public readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, PropertyGroupCollection>(x => x.PropertyGroups);
public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<PropertyType>>(x => x.PropertyTypes);
public readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.HasPropertyTypeBeenRemoved);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Name);
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.ParentId);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.SortOrder);
private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.Level);
private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Path);
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Alias);
private static readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Description);
private static readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Icon);
private static readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Thumbnail);
private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, int>(x => x.CreatorId);
private static readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.AllowedAsRoot);
private static readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.IsContainer);
private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.Trashed);
private static readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<ContentTypeSort>>(x => x.AllowedContentTypes);
private static readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, PropertyGroupCollection>(x => x.PropertyGroups);
private static readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<PropertyType>>(x => x.PropertyTypes);
private static readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.HasPropertyTypeBeenRemoved);
protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector);
OnPropertyChanged(PropertyGroupCollectionSelector);
}
protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
OnPropertyChanged(PropertyTypeCollectionSelector);
}
/// <summary>
@@ -120,7 +115,7 @@ namespace Umbraco.Core.Models
set
{
_parentId = new Lazy<int>(() => value);
OnPropertyChanged(Ps.Value.ParentIdSelector);
OnPropertyChanged(ParentIdSelector);
}
}
@@ -131,7 +126,14 @@ namespace Umbraco.Core.Models
public virtual string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -141,7 +143,14 @@ namespace Umbraco.Core.Models
public virtual int Level //NOTE Is this relevant for a ContentType?
{
get { return _level; }
set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_level = value;
return _level;
}, _level, LevelSelector);
}
}
/// <summary>
@@ -151,7 +160,14 @@ namespace Umbraco.Core.Models
public virtual string Path //NOTE Is this relevant for a ContentType?
{
get { return _path; }
set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_path = value;
return _path;
}, _path, PathSelector);
}
}
/// <summary>
@@ -163,10 +179,12 @@ namespace Umbraco.Core.Models
get { return _alias; }
set
{
SetPropertyValueAndDetectChanges(
value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase),
ref _alias,
Ps.Value.AliasSelector);
SetPropertyValueAndDetectChanges(o =>
{
//_alias = value.ToSafeAlias();
_alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase);
return _alias;
}, _alias, AliasSelector);
}
}
@@ -177,7 +195,14 @@ namespace Umbraco.Core.Models
public virtual string Description
{
get { return _description; }
set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_description = value;
return _description;
}, _description, DescriptionSelector);
}
}
/// <summary>
@@ -187,7 +212,14 @@ namespace Umbraco.Core.Models
public virtual int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
@@ -197,7 +229,14 @@ namespace Umbraco.Core.Models
public virtual string Icon
{
get { return _icon; }
set { SetPropertyValueAndDetectChanges(value, ref _icon, Ps.Value.IconSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_icon = value;
return _icon;
}, _icon, IconSelector);
}
}
/// <summary>
@@ -207,7 +246,14 @@ namespace Umbraco.Core.Models
public virtual string Thumbnail
{
get { return _thumbnail; }
set { SetPropertyValueAndDetectChanges(value, ref _thumbnail, Ps.Value.ThumbnailSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_thumbnail = value;
return _thumbnail;
}, _thumbnail, ThumbnailSelector);
}
}
/// <summary>
@@ -217,7 +263,14 @@ namespace Umbraco.Core.Models
public virtual int CreatorId
{
get { return _creatorId; }
set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_creatorId = value;
return _creatorId;
}, _creatorId, CreatorIdSelector);
}
}
/// <summary>
@@ -227,7 +280,14 @@ namespace Umbraco.Core.Models
public virtual bool AllowedAsRoot
{
get { return _allowedAsRoot; }
set { SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, Ps.Value.AllowedAsRootSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_allowedAsRoot = value;
return _allowedAsRoot;
}, _allowedAsRoot, AllowedAsRootSelector);
}
}
/// <summary>
@@ -240,7 +300,14 @@ namespace Umbraco.Core.Models
public virtual bool IsContainer
{
get { return _isContainer; }
set { SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_isContainer = value;
return _isContainer;
}, _isContainer, IsContainerSelector);
}
}
/// <summary>
@@ -251,7 +318,14 @@ namespace Umbraco.Core.Models
public virtual bool Trashed //NOTE Is this relevant for a ContentType?
{
get { return _trashed; }
set { SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_trashed = value;
return _trashed;
}, _trashed, TrashedSelector);
}
}
private IDictionary<string, object> _additionalData;
@@ -273,11 +347,15 @@ namespace Umbraco.Core.Models
get { return _allowedContentTypes; }
set
{
SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, Ps.Value.AllowedContentTypesSelector,
SetPropertyValueAndDetectChanges(o =>
{
_allowedContentTypes = value;
return _allowedContentTypes;
}, _allowedContentTypes, AllowedContentTypesSelector,
//Custom comparer for enumerable
new DelegateEqualityComparer<IEnumerable<ContentTypeSort>>(
(sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable),
sorts => sorts.GetHashCode()));
sorts => sorts.GetHashCode()));
}
}
@@ -326,7 +404,7 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// <summary>
/// A boolean flag indicating if a property type has been removed from this instance.
/// </summary>
/// <remarks>
@@ -340,7 +418,7 @@ namespace Umbraco.Core.Models
private set
{
_hasPropertyTypeBeenRemoved = value;
OnPropertyChanged(Ps.Value.HasPropertyTypeBeenRemovedSelector);
OnPropertyChanged(HasPropertyTypeBeenRemovedSelector);
}
}
@@ -465,7 +543,7 @@ namespace Umbraco.Core.Models
// actually remove the group
PropertyGroups.RemoveItem(propertyGroupName);
OnPropertyChanged(Ps.Value.PropertyGroupCollectionSelector);
OnPropertyChanged(PropertyGroupCollectionSelector);
}
/// <summary>
@@ -32,13 +32,9 @@ namespace Umbraco.Core.Models
AddContentType(parent);
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ContentTypeCompositionSelector =
ExpressionHelper.GetPropertyInfo<ContentTypeCompositionBase, IEnumerable<IContentTypeComposition>>(x => x.ContentTypeComposition);
}
private static readonly PropertyInfo ContentTypeCompositionSelector =
ExpressionHelper.GetPropertyInfo<ContentTypeCompositionBase, IEnumerable<IContentTypeComposition>>(
x => x.ContentTypeComposition);
/// <summary>
/// Gets or sets the content types that compose this content type.
@@ -50,7 +46,7 @@ namespace Umbraco.Core.Models
set
{
_contentTypeComposition = value.ToList();
OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector);
OnPropertyChanged(ContentTypeCompositionSelector);
}
}
@@ -106,7 +102,7 @@ namespace Umbraco.Core.Models
throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray());
_contentTypeComposition.Add(contentType);
OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector);
OnPropertyChanged(ContentTypeCompositionSelector);
return true;
}
return false;
@@ -132,7 +128,7 @@ namespace Umbraco.Core.Models
if (compositionIdsToRemove.Any())
RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove);
OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector);
OnPropertyChanged(ContentTypeCompositionSelector);
return _contentTypeComposition.Remove(contentTypeComposition);
}
return false;
+73 -23
View File
@@ -65,20 +65,15 @@ namespace Umbraco.Core.Models
_additionalData = new Dictionary<string, object>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.Name);
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.ParentId);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.SortOrder);
public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.Level);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.Path);
public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.CreatorId);
public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, bool>(x => x.Trashed);
public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.PropertyEditorAlias);
public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, DataTypeDatabaseType>(x => x.DatabaseType);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.Name);
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.ParentId);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.SortOrder);
private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.Level);
private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.Path);
private static readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.CreatorId);
private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, bool>(x => x.Trashed);
private static readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.PropertyEditorAlias);
private static readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, DataTypeDatabaseType>(x => x.DatabaseType);
/// <summary>
/// Gets or sets the Id of the Parent entity
@@ -88,7 +83,14 @@ namespace Umbraco.Core.Models
public int ParentId
{
get { return _parentId; }
set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_parentId = value;
return _parentId;
}, _parentId, ParentIdSelector);
}
}
/// <summary>
@@ -98,7 +100,14 @@ namespace Umbraco.Core.Models
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -108,7 +117,14 @@ namespace Umbraco.Core.Models
public int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
@@ -118,7 +134,14 @@ namespace Umbraco.Core.Models
public int Level
{
get { return _level; }
set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_level = value;
return _level;
}, _level, LevelSelector);
}
}
/// <summary>
@@ -128,7 +151,14 @@ namespace Umbraco.Core.Models
public string Path //Setting this value should be handled by the class not the user
{
get { return _path; }
set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_path = value;
return _path;
}, _path, PathSelector);
}
}
/// <summary>
@@ -138,7 +168,14 @@ namespace Umbraco.Core.Models
public int CreatorId
{
get { return _creatorId; }
set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_creatorId = value;
return _creatorId;
}, _creatorId, UserIdSelector);
}
}
//NOTE: SD: Why do we have this ??
@@ -152,7 +189,11 @@ namespace Umbraco.Core.Models
get { return _trashed; }
internal set
{
SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector);
SetPropertyValueAndDetectChanges(o =>
{
_trashed = value;
return _trashed;
}, _trashed, TrashedSelector);
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["Trashed"] = value;
}
@@ -164,7 +205,11 @@ namespace Umbraco.Core.Models
get { return _propertyEditorAlias; }
set
{
SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector);
SetPropertyValueAndDetectChanges(o =>
{
_propertyEditorAlias = value;
return _propertyEditorAlias;
}, _propertyEditorAlias, PropertyEditorAliasSelector);
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["DatabaseType"] = value;
}
@@ -200,7 +245,12 @@ namespace Umbraco.Core.Models
get { return _databaseType; }
set
{
SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector);
SetPropertyValueAndDetectChanges(o =>
{
_databaseType = value;
return _databaseType;
}, _databaseType, DatabaseTypeSelector);
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["DatabaseType"] = value;
}
+31 -18
View File
@@ -30,14 +30,9 @@ namespace Umbraco.Core.Models
_translations = new List<IDictionaryTranslation>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, Guid?>(x => x.ParentId);
public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, string>(x => x.ItemKey);
public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, IEnumerable<IDictionaryTranslation>>(x => x.Translations);
}
private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, Guid?>(x => x.ParentId);
private static readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, string>(x => x.ItemKey);
private static readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, IEnumerable<IDictionaryTranslation>>(x => x.Translations);
/// <summary>
/// Gets or Sets the Parent Id of the Dictionary Item
@@ -46,7 +41,14 @@ namespace Umbraco.Core.Models
public Guid? ParentId
{
get { return _parentId; }
set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_parentId = value;
return _parentId;
}, _parentId, ParentIdSelector);
}
}
/// <summary>
@@ -56,7 +58,14 @@ namespace Umbraco.Core.Models
public string ItemKey
{
get { return _itemKey; }
set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_itemKey = value;
return _itemKey;
}, _itemKey, ItemKeySelector);
}
}
/// <summary>
@@ -68,21 +77,25 @@ namespace Umbraco.Core.Models
get { return _translations; }
set
{
var asArray = value.ToArray();
//ensure the language callback is set on each translation
if (GetLanguage != null)
SetPropertyValueAndDetectChanges(o =>
{
foreach (var translation in asArray.OfType<DictionaryTranslation>())
var asArray = value.ToArray();
//ensure the language callback is set on each translation
if (GetLanguage != null)
{
translation.GetLanguage = GetLanguage;
foreach (var translation in asArray.OfType<DictionaryTranslation>())
{
translation.GetLanguage = GetLanguage;
}
}
}
SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector,
_translations = asArray;
return _translations;
}, _translations, TranslationsSelector,
//Custom comparer for enumerable
new DelegateEqualityComparer<IEnumerable<IDictionaryTranslation>>(
(enumerable, translations) => enumerable.UnsortedSequenceEqual(translations),
enumerable => enumerable.GetHashCode()));
enumerable => enumerable.GetHashCode()));
}
}
}
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Models
private string _value;
//note: this will be memberwise cloned
private int _languageId;
public DictionaryTranslation(ILanguage language, string value)
{
if (language == null) throw new ArgumentNullException("language");
@@ -50,13 +50,8 @@ namespace Umbraco.Core.Models
Key = uniqueId;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, ILanguage>(x => x.Language);
public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, string>(x => x.Value);
}
private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, ILanguage>(x => x.Language);
private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, string>(x => x.Value);
/// <summary>
/// Gets or sets the <see cref="Language"/> for the translation
@@ -84,8 +79,12 @@ namespace Umbraco.Core.Models
}
set
{
SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector);
_languageId = _language == null ? -1 : _language.Id;
SetPropertyValueAndDetectChanges(o =>
{
_language = value;
_languageId = _language == null ? -1 : _language.Id;
return _language;
}, _language, LanguageSelector);
}
}
@@ -101,7 +100,14 @@ namespace Umbraco.Core.Models
public string Value
{
get { return _value; }
set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_value = value;
return _value;
}, _value, ValueSelector);
}
}
public override object DeepClone()
@@ -16,11 +16,6 @@ namespace Umbraco.Core.Models.Editors
/// </remarks>
public class ContentPropertyData
{
public ContentPropertyData(object value, PreValueCollection preValues)
: this(value, preValues, new Dictionary<string, object>())
{
}
public ContentPropertyData(object value, PreValueCollection preValues, IDictionary<string, object> additionalData)
{
Value = value;
@@ -33,9 +28,6 @@ namespace Umbraco.Core.Models.Editors
/// </summary>
public object Value { get; private set; }
/// <summary>
/// The pre-value collection for the content property
/// </summary>
public PreValueCollection PreValues { get; private set; }
/// <summary>
+60 -25
View File
@@ -23,17 +23,13 @@ namespace Umbraco.Core.Models.EntityBase
private DateTime _updateDate;
private bool _wasCancelled;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<Entity, int>(x => x.Id);
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<Entity, Guid>(x => x.Key);
public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo<Entity, DateTime>(x => x.CreateDate);
public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo<Entity, DateTime>(x => x.UpdateDate);
public readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo<Entity, bool>(x => x.HasIdentity);
public readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo<Entity, bool>(x => x.WasCancelled);
}
private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<Entity, int>(x => x.Id);
private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<Entity, Guid>(x => x.Key);
private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo<Entity, DateTime>(x => x.CreateDate);
private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo<Entity, DateTime>(x => x.UpdateDate);
private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo<Entity, bool>(x => x.HasIdentity);
private static readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo<Entity, bool>(x => x.WasCancelled);
/// <summary>
/// Integer Id
@@ -41,11 +37,18 @@ namespace Umbraco.Core.Models.EntityBase
[DataMember]
public int Id
{
get { return _id; }
get
{
return _id;
}
set
{
SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector);
HasIdentity = true; //set the has Identity
SetPropertyValueAndDetectChanges(o =>
{
_id = value;
HasIdentity = true; //set the has Identity
return _id;
}, _id, IdSelector);
}
}
@@ -64,7 +67,14 @@ namespace Umbraco.Core.Models.EntityBase
_key = Guid.NewGuid();
return _key;
}
set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_key = value;
return _key;
}, _key, KeySelector);
}
}
/// <summary>
@@ -74,7 +84,14 @@ namespace Umbraco.Core.Models.EntityBase
public DateTime CreateDate
{
get { return _createDate; }
set { SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_createDate = value;
return _createDate;
}, _createDate, CreateDateSelector);
}
}
/// <summary>
@@ -88,7 +105,14 @@ namespace Umbraco.Core.Models.EntityBase
internal bool WasCancelled
{
get { return _wasCancelled; }
set { SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_wasCancelled = value;
return _wasCancelled;
}, _wasCancelled, WasCancelledSelector);
}
}
/// <summary>
@@ -98,7 +122,14 @@ namespace Umbraco.Core.Models.EntityBase
public DateTime UpdateDate
{
get { return _updateDate; }
set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_updateDate = value;
return _updateDate;
}, _updateDate, UpdateDateSelector);
}
}
internal virtual void ResetIdentity()
@@ -113,10 +144,8 @@ namespace Umbraco.Core.Models.EntityBase
/// </summary>
internal virtual void AddingEntity()
{
if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime))
CreateDate = DateTime.Now;
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
UpdateDate = CreateDate;
CreateDate = DateTime.Now;
UpdateDate = DateTime.Now;
}
/// <summary>
@@ -124,8 +153,7 @@ namespace Umbraco.Core.Models.EntityBase
/// </summary>
internal virtual void UpdatingEntity()
{
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
UpdateDate = DateTime.Now;
UpdateDate = DateTime.Now;
}
/// <summary>
@@ -138,7 +166,14 @@ namespace Umbraco.Core.Models.EntityBase
{
return _hasIdentity;
}
protected set { SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector); }
protected set
{
SetPropertyValueAndDetectChanges(o =>
{
_hasIdentity = value;
return _hasIdentity;
}, _hasIdentity, HasIdentitySelector);
}
}
//TODO: Make this NOT virtual or even exist really!
@@ -18,9 +18,7 @@ namespace Umbraco.Core.Models.EntityBase
//TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662
public virtual IEnumerable<string> GetDirtyProperties()
{
return _propertyChangedInfo == null
? Enumerable.Empty<string>()
: _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
return _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
private bool _changeTrackingEnabled = true;
@@ -28,12 +26,12 @@ namespace Umbraco.Core.Models.EntityBase
/// <summary>
/// Tracks the properties that have changed
/// </summary>
private IDictionary<string, bool> _propertyChangedInfo;
private IDictionary<string, bool> _propertyChangedInfo = new Dictionary<string, bool>();
/// <summary>
/// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties)
/// </summary>
private IDictionary<string, bool> _lastPropertyChangedInfo;
private IDictionary<string, bool> _lastPropertyChangedInfo = null;
/// <summary>
/// Property changed event
@@ -49,13 +47,12 @@ namespace Umbraco.Core.Models.EntityBase
//return if we're not tracking changes
if (_changeTrackingEnabled == false) return;
if (_propertyChangedInfo == null)
_propertyChangedInfo = new Dictionary<string, bool>();
_propertyChangedInfo[propertyInfo.Name] = true;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name));
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name));
}
}
/// <summary>
@@ -65,7 +62,7 @@ namespace Umbraco.Core.Models.EntityBase
/// <returns>True if Property is dirty, otherwise False</returns>
public virtual bool IsPropertyDirty(string propertyName)
{
return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName);
return _propertyChangedInfo.Any(x => x.Key == propertyName);
}
/// <summary>
@@ -74,7 +71,7 @@ namespace Umbraco.Core.Models.EntityBase
/// <returns>True if entity is dirty, otherwise False</returns>
public virtual bool IsDirty()
{
return _propertyChangedInfo != null && _propertyChangedInfo.Any();
return _propertyChangedInfo.Any();
}
/// <summary>
@@ -93,7 +90,7 @@ namespace Umbraco.Core.Models.EntityBase
/// <returns>True if Property was changed, otherwise False. Returns false if the entity had not been previously changed.</returns>
public virtual bool WasPropertyDirty(string propertyName)
{
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName);
return WasDirty() && _lastPropertyChangedInfo.Any(x => x.Key == propertyName);
}
/// <summary>
@@ -103,7 +100,7 @@ namespace Umbraco.Core.Models.EntityBase
{
//NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_lastPropertyChangedInfo = null;
_lastPropertyChangedInfo = new Dictionary<string, bool>();
}
/// <summary>
@@ -133,29 +130,26 @@ namespace Umbraco.Core.Models.EntityBase
if (rememberPreviouslyChangedProperties)
{
//copy the changed properties to the last changed properties
if (_propertyChangedInfo != null)
{
_lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value);
}
_lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value);
}
//NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_propertyChangedInfo = null;
_propertyChangedInfo = new Dictionary<string, bool>();
}
public void ResetChangeTrackingCollections()
protected void ResetChangeTrackingCollections()
{
_propertyChangedInfo = null;
_lastPropertyChangedInfo = null;
_propertyChangedInfo = new Dictionary<string, bool>();
_lastPropertyChangedInfo = new Dictionary<string, bool>();
}
public void DisableChangeTracking()
protected void DisableChangeTracking()
{
_changeTrackingEnabled = false;
}
public void EnableChangeTracking()
protected void EnableChangeTracking()
{
_changeTrackingEnabled = true;
}
@@ -164,61 +158,60 @@ namespace Umbraco.Core.Models.EntityBase
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
/// </summary>
/// <param name="newVal"></param>
/// <param name="origVal"></param>
/// <param name="setValue"></param>
/// <param name="value"></param>
/// <param name="propertySelector"></param>
/// <returns>returns true if the value changed</returns>
/// <remarks>
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// to the same value, so it's really not dirty.
/// </remarks>
internal void SetPropertyValueAndDetectChanges<T>(T newVal, ref T origVal, PropertyInfo propertySelector)
internal bool SetPropertyValueAndDetectChanges<T>(Func<T, T> setValue, T value, PropertyInfo propertySelector)
{
if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom<IEnumerable>(typeof(T)))
{
throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required");
}
SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer<T>.Default);
return SetPropertyValueAndDetectChanges(setValue, value, propertySelector,
new DelegateEqualityComparer<T>(
//Standard Equals comparison
(arg1, arg2) => Equals(arg1, arg2),
arg => arg.GetHashCode()));
}
/// <summary>
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
/// </summary>
/// <param name="newVal"></param>
/// <param name="origVal"></param>
/// <param name="setValue"></param>
/// <param name="value"></param>
/// <param name="propertySelector"></param>
/// <param name="comparer">The equality comparer to use</param>
/// <returns>returns true if the value changed</returns>
/// <remarks>
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// to the same value, so it's really not dirty.
/// </remarks>
internal void SetPropertyValueAndDetectChanges<T>(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer<T> comparer)
internal bool SetPropertyValueAndDetectChanges<T>(Func<T, T> setValue, T value, PropertyInfo propertySelector, IEqualityComparer<T> comparer)
{
//don't track changes, just set the value
if (_changeTrackingEnabled == false)
{
//set the original value
origVal = newVal;
}
else
{
//check changed
var changed = comparer.Equals(origVal, newVal) == false;
var initVal = value;
var newVal = setValue(value);
//set the original value
origVal = newVal;
//don't track changes, just set the value (above)
if (_changeTrackingEnabled == false) return false;
//raise the event if it was changed
if (changed)
{
OnPropertyChanged(propertySelector);
}
if (comparer.Equals(initVal, newVal) == false)
{
OnPropertyChanged(propertySelector);
return true;
}
return false;
}
}
}
+12 -12
View File
@@ -33,14 +33,8 @@ namespace Umbraco.Core.Models
_content = getFileContent != null ? null : string.Empty;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Content);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Path);
}
private static readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Content);
private static readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Path);
private string _alias;
private string _name;
@@ -93,7 +87,11 @@ namespace Umbraco.Core.Models
_alias = null;
_name = null;
SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector);
SetPropertyValueAndDetectChanges(o =>
{
_path = SanitizePath(value);
return _path;
}, _path, PathSelector);
}
}
@@ -133,9 +131,11 @@ namespace Umbraco.Core.Models
}
set
{
SetPropertyValueAndDetectChanges(
value ?? string.Empty, // cannot set to null
ref _content, Ps.Value.ContentSelector);
SetPropertyValueAndDetectChanges(o =>
{
_content = value ?? string.Empty; // cannot set to null
return _content;
}, _content, ContentSelector);
}
}
@@ -1,4 +1,3 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
@@ -12,9 +11,6 @@ namespace Umbraco.Core.Models
[DataMember]
int Id { get; set; }
[DataMember]
Guid Key { get; set; }
/// <summary>
/// Gets or sets the Alias of the Property
/// </summary>
+1 -2
View File
@@ -1,5 +1,4 @@
using System;
using Semver;
using Semver;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
-37
View File
@@ -1,37 +0,0 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a redirect url.
/// </summary>
public interface IRedirectUrl : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the identifier of the content item.
/// </summary>
[DataMember]
int ContentId { get; set; }
/// <summary>
/// Gets or sets the unique key identifying the content item.
/// </summary>
[DataMember]
Guid ContentKey { get; set; }
/// <summary>
/// Gets or sets the redirect url creation date.
/// </summary>
[DataMember]
DateTime CreateDateUtc { get; set; }
/// <summary>
/// Gets or sets the redirect url route.
/// </summary>
/// <remarks>Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo.</remarks>
[DataMember]
string Url { get; set; }
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace Umbraco.Core.Models
{
public interface IXsltFile : IFile
{
}
}
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Models.Identity
Culture = Configuration.GlobalSettings.DefaultUILanguage;
}
public virtual async Task<ClaimsIdentity> GenerateUserIdentityAsync(BackOfficeUserManager<BackOfficeIdentityUser> manager)
public virtual async Task<ClaimsIdentity> GenerateUserIdentityAsync(BackOfficeUserManager manager)
{
// NOTE the authenticationType must match the umbraco one
// defined in CookieAuthenticationOptions.AuthenticationType
+18 -9
View File
@@ -21,13 +21,8 @@ namespace Umbraco.Core.Models
IsoCode = isoCode;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.IsoCode);
public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.CultureName);
}
private static readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.IsoCode);
private static readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.CultureName);
/// <summary>
/// Gets or sets the Iso Code for the Language
@@ -36,7 +31,14 @@ namespace Umbraco.Core.Models
public string IsoCode
{
get { return _isoCode; }
set { SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_isoCode = value;
return _isoCode;
}, _isoCode, IsoCodeSelector);
}
}
/// <summary>
@@ -46,7 +48,14 @@ namespace Umbraco.Core.Models
public string CultureName
{
get { return _cultureName; }
set { SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cultureName = value;
return _cultureName;
}, _cultureName, CultureNameSelector);
}
}
/// <summary>
+107 -34
View File
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Umbraco.Core.IO;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Strings;
@@ -24,12 +27,11 @@ namespace Umbraco.Core.Models
_addedProperties = new List<string>();
_removedProperties = new List<string>();
}
/// <summary>
/// Creates an item with pre-filled properties
/// </summary>
/// <param name="id"></param>
/// <param name="key"></param>
/// <param name="useInEditor"></param>
/// <param name="cacheDuration"></param>
/// <param name="alias"></param>
@@ -41,11 +43,10 @@ namespace Umbraco.Core.Models
/// <param name="cacheByMember"></param>
/// <param name="dontRender"></param>
/// <param name="scriptPath"></param>
public Macro(int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
public Macro(int id, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
: this()
{
Id = id;
Key = key;
UseInEditor = useInEditor;
CacheDuration = cacheDuration;
Alias = alias.ToCleanString(CleanStringType.Alias);
@@ -113,27 +114,22 @@ namespace Umbraco.Core.Models
private List<string> _addedProperties;
private List<string> _removedProperties;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.Alias);
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.Name);
public readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.UseInEditor);
public readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo<Macro, int>(x => x.CacheDuration);
public readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.CacheByPage);
public readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.CacheByMember);
public readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.DontRender);
public readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ControlType);
public readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ControlAssembly);
public readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ScriptPath);
public readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.XsltPath);
public readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo<Macro, MacroPropertyCollection>(x => x.Properties);
}
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.Alias);
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.Name);
private static readonly PropertyInfo UseInEditorSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.UseInEditor);
private static readonly PropertyInfo CacheDurationSelector = ExpressionHelper.GetPropertyInfo<Macro, int>(x => x.CacheDuration);
private static readonly PropertyInfo CacheByPageSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.CacheByPage);
private static readonly PropertyInfo CacheByMemberSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.CacheByMember);
private static readonly PropertyInfo DontRenderSelector = ExpressionHelper.GetPropertyInfo<Macro, bool>(x => x.DontRender);
private static readonly PropertyInfo ControlPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ControlType);
private static readonly PropertyInfo ControlAssemblySelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ControlAssembly);
private static readonly PropertyInfo ScriptPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.ScriptPath);
private static readonly PropertyInfo XsltPathSelector = ExpressionHelper.GetPropertyInfo<Macro, string>(x => x.XsltPath);
private static readonly PropertyInfo PropertiesSelector = ExpressionHelper.GetPropertyInfo<Macro, MacroPropertyCollection>(x => x.Properties);
void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PropertiesSelector);
OnPropertyChanged(PropertiesSelector);
if (e.Action == NotifyCollectionChangedAction.Add)
{
@@ -171,7 +167,7 @@ namespace Umbraco.Core.Models
/// <param name="e"></param>
void PropertyDataChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PropertiesSelector);
OnPropertyChanged(PropertiesSelector);
}
public override void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
@@ -208,7 +204,14 @@ namespace Umbraco.Core.Models
public string Alias
{
get { return _alias; }
set { SetPropertyValueAndDetectChanges(value.ToCleanString(CleanStringType.Alias), ref _alias, Ps.Value.AliasSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_alias = value.ToCleanString(CleanStringType.Alias);
return _alias;
}, _alias, AliasSelector);
}
}
/// <summary>
@@ -218,7 +221,14 @@ namespace Umbraco.Core.Models
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -228,7 +238,14 @@ namespace Umbraco.Core.Models
public bool UseInEditor
{
get { return _useInEditor; }
set { SetPropertyValueAndDetectChanges(value, ref _useInEditor, Ps.Value.UseInEditorSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_useInEditor = value;
return _useInEditor;
}, _useInEditor, UseInEditorSelector);
}
}
/// <summary>
@@ -238,7 +255,14 @@ namespace Umbraco.Core.Models
public int CacheDuration
{
get { return _cacheDuration; }
set { SetPropertyValueAndDetectChanges(value, ref _cacheDuration, Ps.Value.CacheDurationSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cacheDuration = value;
return _cacheDuration;
}, _cacheDuration, CacheDurationSelector);
}
}
/// <summary>
@@ -248,7 +272,14 @@ namespace Umbraco.Core.Models
public bool CacheByPage
{
get { return _cacheByPage; }
set { SetPropertyValueAndDetectChanges(value, ref _cacheByPage, Ps.Value.CacheByPageSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cacheByPage = value;
return _cacheByPage;
}, _cacheByPage, CacheByPageSelector);
}
}
/// <summary>
@@ -258,7 +289,14 @@ namespace Umbraco.Core.Models
public bool CacheByMember
{
get { return _cacheByMember; }
set { SetPropertyValueAndDetectChanges(value, ref _cacheByMember, Ps.Value.CacheByMemberSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_cacheByMember = value;
return _cacheByMember;
}, _cacheByMember, CacheByMemberSelector);
}
}
/// <summary>
@@ -268,7 +306,14 @@ namespace Umbraco.Core.Models
public bool DontRender
{
get { return _dontRender; }
set { SetPropertyValueAndDetectChanges(value, ref _dontRender, Ps.Value.DontRenderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_dontRender = value;
return _dontRender;
}, _dontRender, DontRenderSelector);
}
}
/// <summary>
@@ -278,7 +323,14 @@ namespace Umbraco.Core.Models
public string ControlType
{
get { return _scriptFile; }
set { SetPropertyValueAndDetectChanges(value, ref _scriptFile, Ps.Value.ControlPathSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_scriptFile = value;
return _scriptFile;
}, _scriptFile, ControlPathSelector);
}
}
/// <summary>
@@ -289,7 +341,14 @@ namespace Umbraco.Core.Models
public string ControlAssembly
{
get { return _scriptAssembly; }
set { SetPropertyValueAndDetectChanges(value, ref _scriptAssembly, Ps.Value.ControlAssemblySelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_scriptAssembly = value;
return _scriptAssembly;
}, _scriptAssembly, ControlAssemblySelector);
}
}
/// <summary>
@@ -300,7 +359,14 @@ namespace Umbraco.Core.Models
public string ScriptPath
{
get { return _scriptPath; }
set { SetPropertyValueAndDetectChanges(value, ref _scriptPath, Ps.Value.ScriptPathSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_scriptPath = value;
return _scriptPath;
}, _scriptPath, ScriptPathSelector);
}
}
/// <summary>
@@ -311,7 +377,14 @@ namespace Umbraco.Core.Models
public string XsltPath
{
get { return _xslt; }
set { SetPropertyValueAndDetectChanges(value, ref _xslt, Ps.Value.XsltPathSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_xslt = value;
return _xslt;
}, _xslt, XsltPathSelector);
}
}
/// <summary>
+54 -35
View File
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models
{
public MacroProperty()
{
_key = Guid.NewGuid();
}
/// <summary>
@@ -30,7 +30,6 @@ namespace Umbraco.Core.Models
_alias = alias;
_name = name;
_sortOrder = sortOrder;
_key = Guid.NewGuid();
//try to get the new mapped parameter editor
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false);
@@ -46,18 +45,16 @@ namespace Umbraco.Core.Models
/// Ctor for creating an existing property
/// </summary>
/// <param name="id"></param>
/// <param name="key"></param>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="sortOrder"></param>
/// <param name="editorAlias"></param>
internal MacroProperty(int id, Guid key, string @alias, string name, int sortOrder, string editorAlias)
internal MacroProperty(int id, string @alias, string name, int sortOrder, string editorAlias)
{
_id = id;
_alias = alias;
_name = name;
_sortOrder = sortOrder;
_key = key;
//try to get the new mapped parameter editor
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false);
@@ -69,34 +66,17 @@ namespace Umbraco.Core.Models
_editorAlias = editorAlias;
}
private Guid _key;
private string _alias;
private string _name;
private int _sortOrder;
private int _id;
private string _editorAlias;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<MacroProperty, Guid>(x => x.Key);
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Alias);
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Name);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, int>(x => x.SortOrder);
public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<Entity, int>(x => x.Id);
public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.EditorAlias);
}
/// <summary>
/// Gets or sets the Key of the Property
/// </summary>
[DataMember]
public Guid Key
{
get { return _key; }
set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); }
}
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Alias);
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Name);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, int>(x => x.SortOrder);
private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<Entity, int>(x => x.Id);
private static readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.EditorAlias);
/// <summary>
/// Gets or sets the Alias of the Property
@@ -105,7 +85,14 @@ namespace Umbraco.Core.Models
public int Id
{
get { return _id; }
set { SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_id = value;
return _alias;
}, _alias, IdSelector);
}
}
/// <summary>
@@ -115,7 +102,14 @@ namespace Umbraco.Core.Models
public string Alias
{
get { return _alias; }
set { SetPropertyValueAndDetectChanges(value, ref _alias, Ps.Value.AliasSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_alias = value;
return _alias;
}, _alias, AliasSelector);
}
}
/// <summary>
@@ -125,7 +119,14 @@ namespace Umbraco.Core.Models
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -135,7 +136,14 @@ namespace Umbraco.Core.Models
public int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
@@ -151,10 +159,21 @@ namespace Umbraco.Core.Models
get { return _editorAlias; }
set
{
//try to get the new mapped parameter editor
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false);
var newVal = mapped.IsNullOrWhiteSpace() == false ? mapped : value;
SetPropertyValueAndDetectChanges(newVal, ref _editorAlias, Ps.Value.PropertyTypeSelector);
SetPropertyValueAndDetectChanges(o =>
{
//try to get the new mapped parameter editor
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(value, false);
if (mapped.IsNullOrWhiteSpace() == false)
{
_editorAlias = mapped;
}
else
{
_editorAlias = value;
}
return _editorAlias;
}, _editorAlias, PropertyTypeSelector);
}
}
+51 -33
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -8,56 +9,73 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Core.Models
{
public static class MediaExtensions
internal static class MediaExtensions
{
/// <summary>
/// Gets the url of a media item.
/// Hack: we need to put this in a real place, this is currently just used to render the urls for a media item in the back office
/// </summary>
/// <returns></returns>
public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger)
{
var propertyType = media.PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyAlias));
if (propertyType == null) return string.Empty;
var val = media.Properties[propertyType];
if (val == null) return string.Empty;
var jsonString = val.Value as string;
if (jsonString == null) return string.Empty;
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
return jsonString;
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)
if (propertyType != null)
{
if (jsonString.DetectIsJson() == false)
return jsonString;
try
var val = media.Properties[propertyType];
if (val != null)
{
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
if (json["src"] != null)
return json["src"].Value<string>();
}
catch (Exception ex)
{
logger.Error<ImageCropperValueConverter>("Could not parse the string " + jsonString + " to a json object", ex);
return string.Empty;
var jsonString = val.Value as string;
if (jsonString != null)
{
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)
{
if (jsonString.DetectIsJson())
{
try
{
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
if (json["src"] != null)
{
return json["src"].Value<string>();
}
}
catch (Exception ex)
{
logger.Error<ImageCropperValueConverter>("Could not parse the string " + jsonString + " to a json object", ex);
return string.Empty;
}
}
else
{
return jsonString;
}
}
else if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
{
return jsonString;
}
//hrm, without knowing what it is, just adding a string here might not be very nice
}
}
}
// hrm, without knowing what it is, just adding a string here might not be very nice
return string.Empty;
}
/// <summary>
/// Gets the urls of a media item.
/// Hack: we need to put this in a real place, this is currently just used to render the urls for a media item in the back office
/// </summary>
/// <returns></returns>
public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger)
{
return contentSection.ImageAutoFillProperties
.Select(field => media.GetUrl(field.Alias, logger))
.Where(link => string.IsNullOrWhiteSpace(link) == false)
.ToArray();
var links = new List<string>();
var autoFillProperties = contentSection.ImageAutoFillProperties.ToArray();
if (autoFillProperties.Any())
{
links.AddRange(
autoFillProperties
.Select(field => media.GetUrl(field.Alias, logger))
.Where(link => link.IsNullOrWhiteSpace() == false));
}
return links.ToArray();
}
}
}
+36 -13
View File
@@ -109,15 +109,10 @@ namespace Umbraco.Core.Models
IsApproved = true;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo<Member, string>(x => x.Username);
public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo<Member, string>(x => x.Email);
public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo<Member, string>(x => x.RawPasswordValue);
public readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo<Member, object>(x => x.ProviderUserKey);
}
private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo<Member, string>(x => x.Username);
private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo<Member, string>(x => x.Email);
private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo<Member, string>(x => x.RawPasswordValue);
private static readonly PropertyInfo ProviderUserKeySelector = ExpressionHelper.GetPropertyInfo<Member, object>(x => x.ProviderUserKey);
/// <summary>
/// Gets or sets the Username
@@ -126,7 +121,14 @@ namespace Umbraco.Core.Models
public string Username
{
get { return _username; }
set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_username = value;
return _username;
}, _username, UsernameSelector);
}
}
/// <summary>
@@ -136,7 +138,14 @@ namespace Umbraco.Core.Models
public string Email
{
get { return _email; }
set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_email = value;
return _email;
}, _email, EmailSelector);
}
}
/// <summary>
@@ -146,7 +155,14 @@ namespace Umbraco.Core.Models
public string RawPasswordValue
{
get { return _rawPasswordValue; }
set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_rawPasswordValue = value;
return _rawPasswordValue;
}, _rawPasswordValue, PasswordSelector);
}
}
/// <summary>
@@ -474,7 +490,14 @@ namespace Umbraco.Core.Models
{
return _providerUserKey;
}
set { SetPropertyValueAndDetectChanges(value, ref _providerUserKey, Ps.Value.ProviderUserKeySelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_providerUserKey = value;
return _providerUserKey;
}, _providerUserKey, ProviderUserKeySelector);
}
}
+21 -15
View File
@@ -21,13 +21,8 @@ namespace Umbraco.Core.Models
private string _name;
private int _creatorId;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MemberGroup, string>(x => x.Name);
public readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo<MemberGroup, int>(x => x.CreatorId);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MemberGroup, string>(x => x.Name);
private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo<MemberGroup, int>(x => x.CreatorId);
[DataMember]
public string Name
@@ -35,22 +30,33 @@ namespace Umbraco.Core.Models
get { return _name; }
set
{
if (_name != value)
SetPropertyValueAndDetectChanges(o =>
{
//if the name has changed, add the value to the additional data,
//this is required purely for event handlers to know the previous name of the group
//so we can keep the public access up to date.
AdditionalData["previousName"] = _name;
}
if (_name != value)
{
//if the name has changed, add the value to the additional data,
//this is required purely for event handlers to know the previous name of the group
//so we can keep the public access up to date.
AdditionalData["previousName"] = _name;
}
SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector);
_name = value;
return _name;
}, _name, NameSelector);
}
}
public int CreatorId
{
get { return _creatorId; }
set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.CreatorIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_creatorId = value;
return _creatorId;
}, _creatorId, CreatorIdSelector);
}
}
public IDictionary<string, object> AdditionalData { get; private set; }
+8 -11
View File
@@ -30,12 +30,7 @@ namespace Umbraco.Core.Models
MemberTypePropertyTypes = new Dictionary<string, MemberTypePropertyProfileAccess>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MemberType, string>(x => x.Alias);
}
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MemberType, string>(x => x.Alias);
/// <summary>
/// The Alias of the ContentType
@@ -55,11 +50,13 @@ namespace Umbraco.Core.Models
// .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase)
// Need to ask Stephen
var newVal = value == "_umbracoSystemDefaultProtectType"
? value
: (value == null ? string.Empty : value.ToSafeAlias());
SetPropertyValueAndDetectChanges(newVal, ref _alias, Ps.Value.AliasSelector);
SetPropertyValueAndDetectChanges(o =>
{
_alias = value == "_umbracoSystemDefaultProtectType"
? value
: (value == null ? string.Empty : value.ToSafeAlias() );
return _alias;
}, _alias, AliasSelector);
}
}
+153 -42
View File
@@ -77,32 +77,27 @@ namespace Umbraco.Core.Models.Membership
private bool _defaultToLiveEditing;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private static readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.FailedPasswordAttempts);
private static readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo<User, DateTime>(x => x.LastLockoutDate);
private static readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo<User, DateTime>(x => x.LastLoginDate);
private static readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo<User, DateTime>(x => x.LastPasswordChangeDate);
private class PropertySelectors
{
public readonly PropertyInfo FailedPasswordAttemptsSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.FailedPasswordAttempts);
public readonly PropertyInfo LastLockoutDateSelector = ExpressionHelper.GetPropertyInfo<User, DateTime>(x => x.LastLockoutDate);
public readonly PropertyInfo LastLoginDateSelector = ExpressionHelper.GetPropertyInfo<User, DateTime>(x => x.LastLoginDate);
public readonly PropertyInfo LastPasswordChangeDateSelector = ExpressionHelper.GetPropertyInfo<User, DateTime>(x => x.LastPasswordChangeDate);
private static readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.SecurityStamp);
private static readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.SessionTimeout);
private static readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.StartContentId);
private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.StartMediaId);
private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo<User, IEnumerable<string>>(x => x.AllowedSections);
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Name);
private static readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Username);
private static readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Email);
private static readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.RawPasswordValue);
private static readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo<User, bool>(x => x.IsLockedOut);
private static readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo<User, bool>(x => x.IsApproved);
private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Language);
public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.SecurityStamp);
public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.SessionTimeout);
public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.StartContentId);
public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.StartMediaId);
public readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo<User, IEnumerable<string>>(x => x.AllowedSections);
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Name);
public readonly PropertyInfo UsernameSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Username);
public readonly PropertyInfo EmailSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Email);
public readonly PropertyInfo PasswordSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.RawPasswordValue);
public readonly PropertyInfo IsLockedOutSelector = ExpressionHelper.GetPropertyInfo<User, bool>(x => x.IsLockedOut);
public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo<User, bool>(x => x.IsApproved);
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Language);
public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo<User, bool>(x => x.DefaultToLiveEditing);
public readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo<User, IUserType>(x => x.UserType);
}
private static readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo<User, bool>(x => x.DefaultToLiveEditing);
private static readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo<User, IUserType>(x => x.UserType);
#region Implementation of IMembershipUser
@@ -118,61 +113,124 @@ namespace Umbraco.Core.Models.Membership
public string Username
{
get { return _username; }
set { SetPropertyValueAndDetectChanges(value, ref _username, Ps.Value.UsernameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_username = value;
return _username;
}, _username, UsernameSelector);
}
}
[DataMember]
public string Email
{
get { return _email; }
set { SetPropertyValueAndDetectChanges(value, ref _email, Ps.Value.EmailSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_email = value;
return _email;
}, _email, EmailSelector);
}
}
[DataMember]
public string RawPasswordValue
{
get { return _rawPasswordValue; }
set { SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, Ps.Value.PasswordSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_rawPasswordValue = value;
return _rawPasswordValue;
}, _rawPasswordValue, PasswordSelector);
}
}
[DataMember]
public bool IsApproved
{
get { return _isApproved; }
set { SetPropertyValueAndDetectChanges(value, ref _isApproved, Ps.Value.IsApprovedSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_isApproved = value;
return _isApproved;
}, _isApproved, IsApprovedSelector);
}
}
[IgnoreDataMember]
public bool IsLockedOut
{
get { return _isLockedOut; }
set { SetPropertyValueAndDetectChanges(value, ref _isLockedOut, Ps.Value.IsLockedOutSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_isLockedOut = value;
return _isLockedOut;
}, _isLockedOut, IsLockedOutSelector);
}
}
[IgnoreDataMember]
public DateTime LastLoginDate
{
get { return _lastLoginDate; }
set { SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, Ps.Value.LastLoginDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_lastLoginDate = value;
return _lastLoginDate;
}, _lastLoginDate, LastLoginDateSelector);
}
}
[IgnoreDataMember]
public DateTime LastPasswordChangeDate
{
get { return _lastPasswordChangedDate; }
set { SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, Ps.Value.LastPasswordChangeDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_lastPasswordChangedDate = value;
return _lastPasswordChangedDate;
}, _lastPasswordChangedDate, LastPasswordChangeDateSelector);
}
}
[IgnoreDataMember]
public DateTime LastLockoutDate
{
get { return _lastLockoutDate; }
set { SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, Ps.Value.LastLockoutDateSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_lastLockoutDate = value;
return _lastLockoutDate;
}, _lastLockoutDate, LastLockoutDateSelector);
}
}
[IgnoreDataMember]
public int FailedPasswordAttempts
{
get { return _failedLoginAttempts; }
set { SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, Ps.Value.FailedPasswordAttemptsSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_failedLoginAttempts = value;
return _failedLoginAttempts;
}, _failedLoginAttempts, FailedPasswordAttemptsSelector);
}
}
//TODO: Figure out how to support all of this! - we cannot have NotImplementedExceptions because these get used by the IMembershipMemberService<IUser> service so
@@ -193,7 +251,14 @@ namespace Umbraco.Core.Models.Membership
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
public IEnumerable<string> AllowedSections
@@ -229,7 +294,14 @@ namespace Umbraco.Core.Models.Membership
public string SecurityStamp
{
get { return _securityStamp; }
set { SetPropertyValueAndDetectChanges(value, ref _securityStamp, Ps.Value.SecurityStampSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_securityStamp = value;
return _securityStamp;
}, _securityStamp, SecurityStampSelector);
}
}
/// <summary>
@@ -258,7 +330,14 @@ namespace Umbraco.Core.Models.Membership
public int SessionTimeout
{
get { return _sessionTimeout; }
set { SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, Ps.Value.SessionTimeoutSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sessionTimeout = value;
return _sessionTimeout;
}, _sessionTimeout, SessionTimeoutSelector);
}
}
/// <summary>
@@ -271,7 +350,14 @@ namespace Umbraco.Core.Models.Membership
public int StartContentId
{
get { return _startContentId; }
set { SetPropertyValueAndDetectChanges(value, ref _startContentId, Ps.Value.StartContentIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_startContentId = value;
return _startContentId;
}, _startContentId, StartContentIdSelector);
}
}
/// <summary>
@@ -284,14 +370,28 @@ namespace Umbraco.Core.Models.Membership
public int StartMediaId
{
get { return _startMediaId; }
set { SetPropertyValueAndDetectChanges(value, ref _startMediaId, Ps.Value.StartMediaIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_startMediaId = value;
return _startMediaId;
}, _startMediaId, StartMediaIdSelector);
}
}
[DataMember]
public string Language
{
get { return _language; }
set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_language = value;
return _language;
}, _language, LanguageSelector);
}
}
//TODO: This should be a private set
@@ -306,7 +406,14 @@ namespace Umbraco.Core.Models.Membership
internal bool DefaultToLiveEditing
{
get { return _defaultToLiveEditing; }
set { SetPropertyValueAndDetectChanges(value, ref _defaultToLiveEditing, Ps.Value.DefaultToLiveEditingSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_defaultToLiveEditing = value;
return _defaultToLiveEditing;
}, _defaultToLiveEditing, DefaultToLiveEditingSelector);
}
}
[IgnoreDataMember]
@@ -320,7 +427,11 @@ namespace Umbraco.Core.Models.Membership
throw new InvalidOperationException("Cannot assign a User Type that has not been persisted");
}
SetPropertyValueAndDetectChanges(value, ref _userType, Ps.Value.UserTypeSelector);
SetPropertyValueAndDetectChanges(o =>
{
_userType = value;
return _userType;
}, _userType, UserTypeSelector);
}
}
@@ -346,7 +457,7 @@ namespace Umbraco.Core.Models.Membership
/// <param name="e"></param>
void SectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.AllowedSectionsSelector);
OnPropertyChanged(AllowedSectionsSelector);
if (e.Action == NotifyCollectionChangedAction.Add)
{
+21 -14
View File
@@ -20,14 +20,9 @@ namespace Umbraco.Core.Models.Membership
private string _name;
private IEnumerable<string> _permissions;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Name);
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Alias);
public readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo<UserType, IEnumerable<string>>(x => x.Permissions);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Name);
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Alias);
private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo<UserType, IEnumerable<string>>(x => x.Permissions);
[DataMember]
public string Alias
@@ -35,10 +30,11 @@ namespace Umbraco.Core.Models.Membership
get { return _alias; }
set
{
SetPropertyValueAndDetectChanges(
value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase),
ref _alias,
Ps.Value.AliasSelector);
SetPropertyValueAndDetectChanges(o =>
{
_alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase);
return _alias;
}, _alias, AliasSelector);
}
}
@@ -46,7 +42,14 @@ namespace Umbraco.Core.Models.Membership
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -61,7 +64,11 @@ namespace Umbraco.Core.Models.Membership
get { return _permissions; }
set
{
SetPropertyValueAndDetectChanges(value, ref _permissions, Ps.Value.PermissionsSelector,
SetPropertyValueAndDetectChanges(o =>
{
_permissions = value;
return _permissions;
}, _permissions, PermissionsSelector,
//Custom comparer for enumerable
new DelegateEqualityComparer<IEnumerable<string>>(
(enum1, enum2) => enum1.UnsortedSequenceEqual(enum2),
+18 -10
View File
@@ -19,27 +19,35 @@ namespace Umbraco.Core.Models
_version = version;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MigrationEntry, string>(x => x.MigrationName);
public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo<MigrationEntry, SemVersion>(x => x.Version);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MigrationEntry, string>(x => x.MigrationName);
private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo<MigrationEntry, SemVersion>(x => x.Version);
private string _migrationName;
private SemVersion _version;
public string MigrationName
{
get { return _migrationName; }
set { SetPropertyValueAndDetectChanges(value, ref _migrationName, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_migrationName = value;
return _migrationName;
}, _migrationName, NameSelector);
}
}
public SemVersion Version
{
get { return _version; }
set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_version = value;
return _version;
}, _version, VersionSelector);
}
}
}
}
+91 -86
View File
@@ -1,9 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
@@ -43,42 +45,8 @@ namespace Umbraco.Core.Models
Value = value;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<Property, object>(x => x.Value);
public readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo<Property, Guid>(x => x.Version);
}
private static readonly DelegateEqualityComparer<object> ValueComparer = new DelegateEqualityComparer<object>(
(o, o1) =>
{
if (o == null && o1 == null) return true;
//custom comparer for strings.
if (o is string || o1 is string)
{
//if one is null and another is empty then they are the same
if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace())
{
return true;
}
if (o == null || o1 == null) return false;
return o.Equals(o1);
}
if (o == null || o1 == null) return false;
//Custom comparer for enumerable if it is enumerable
var enum1 = o as IEnumerable;
var enum2 = o1 as IEnumerable;
if (enum1 != null && enum2 != null)
{
return enum1.Cast<object>().UnsortedSequenceEqual(enum2.Cast<object>());
}
return o.Equals(o1);
}, o => o.GetHashCode());
private static readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<Property, object>(x => x.Value);
private static readonly PropertyInfo VersionSelector = ExpressionHelper.GetPropertyInfo<Property, Guid>(x => x.Version);
/// <summary>
/// Returns the instance of the tag support, by default tags are not enabled
@@ -130,14 +98,14 @@ namespace Umbraco.Core.Models
public Guid Version
{
get { return _version; }
set { SetPropertyValueAndDetectChanges(value, ref _version, Ps.Value.VersionSelector); }
}
private static void ThrowTypeException(object value, Type expected, string alias)
{
throw new InvalidOperationException(string.Format("Value \"{0}\" of type \"{1}\" could not be converted"
+ " to type \"{2}\" which is expected by property type \"{3}\".",
value, value.GetType(), expected, alias));
set
{
SetPropertyValueAndDetectChanges(o =>
{
_version = value;
return _version;
}, _version, VersionSelector);
}
}
/// <summary>
@@ -153,54 +121,91 @@ namespace Umbraco.Core.Models
get { return _value; }
set
{
var isOfExpectedType = _propertyType.IsPropertyTypeValid(value);
bool typeValidation = _propertyType.IsPropertyTypeValid(value);
if (isOfExpectedType == false) // isOfExpectedType is true if value is null - so if false, value is *not* null
if (typeValidation == false)
{
// "garbage-in", accept what we can & convert
// throw only if conversion is not possible
var s = value.ToString();
switch (_propertyType.DataTypeDatabaseType)
// Normally we'll throw an exception here. However if the property is of a type that can have it's data field (dataInt, dataVarchar etc.)
// changed, we might have a value of the now "wrong" type. As of May 2016 Label is the only built-in property editor that supports this.
// In that case we should try to parse the value and return null if that's not possible rather than throwing an exception.
if (value != null && _propertyType.CanHaveDataValueTypeChanged())
{
case DataTypeDatabaseType.Nvarchar:
case DataTypeDatabaseType.Ntext:
value = s;
break;
case DataTypeDatabaseType.Integer:
if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null
else
{
var convInt = value.TryConvertTo<int>();
if (convInt == false) ThrowTypeException(value, typeof(int), _propertyType.Alias);
value = convInt.Result;
}
break;
case DataTypeDatabaseType.Decimal:
if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null
else
{
var convDecimal = value.TryConvertTo<decimal>();
if (convDecimal == false) ThrowTypeException(value, typeof (decimal), _propertyType.Alias);
// need to normalize the value (change the scaling factor and remove trailing zeroes)
// because the underlying database is going to mess with the scaling factor anyways.
value = convDecimal.Result.Normalize();
}
break;
case DataTypeDatabaseType.Date:
if (s.IsNullOrWhiteSpace()) value = null; // assume empty means null
else
{
var convDateTime = value.TryConvertTo<DateTime>();
if (convDateTime == false) ThrowTypeException(value, typeof (DateTime), _propertyType.Alias);
value = convDateTime.Result;
}
break;
var stringValue = value.ToString();
switch (_propertyType.DataTypeDatabaseType)
{
case DataTypeDatabaseType.Nvarchar:
case DataTypeDatabaseType.Ntext:
value = stringValue;
break;
case DataTypeDatabaseType.Integer:
int integerValue;
if (int.TryParse(stringValue, out integerValue) == false)
{
// Edge case, but if changed from decimal --> integer, the above tryparse will fail. So we'll try going
// via decimal too to return the integer value rather than zero.
decimal decimalForIntegerValue;
if (decimal.TryParse(stringValue, out decimalForIntegerValue))
{
integerValue = (int)decimalForIntegerValue;
}
}
value = integerValue;
break;
case DataTypeDatabaseType.Decimal:
decimal decimalValue;
decimal.TryParse(stringValue, out decimalValue);
value = decimalValue;
break;
case DataTypeDatabaseType.Date:
DateTime dateValue;
DateTime.TryParse(stringValue, out dateValue);
value = dateValue;
break;
}
}
else
{
throw new Exception(
string.Format(
"Type validation failed. The value type: '{0}' does not match the DataType in PropertyType with alias: '{1}'",
value == null ? "null" : value.GetType().Name, Alias));
}
}
SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector, ValueComparer);
SetPropertyValueAndDetectChanges(o =>
{
_value = value;
return _value;
}, _value, ValueSelector,
new DelegateEqualityComparer<object>(
(o, o1) =>
{
if (o == null && o1 == null) return true;
//custom comparer for strings.
if (o is string || o1 is string)
{
//if one is null and another is empty then they are the same
if ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace())
{
return true;
}
if (o == null || o1 == null) return false;
return o.Equals(o1);
}
if (o == null || o1 == null) return false;
//Custom comparer for enumerable if it is enumerable
var enum1 = o as IEnumerable;
var enum2 = o1 as IEnumerable;
if (enum1 != null && enum2 != null)
{
return enum1.Cast<object>().UnsortedSequenceEqual(enum2.Cast<object>());
}
return o.Equals(o1);
}, o => o.GetHashCode()));
}
}
+20 -12
View File
@@ -29,18 +29,12 @@ namespace Umbraco.Core.Models
PropertyTypes = propertyTypeCollection;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, string>(x => x.Name);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, int>(x => x.SortOrder);
public readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, PropertyTypeCollection>(x => x.PropertyTypes);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, string>(x => x.Name);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, int>(x => x.SortOrder);
private readonly static PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<PropertyGroup, PropertyTypeCollection>(x => x.PropertyTypes);
void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PropertyTypeCollectionSelector);
OnPropertyChanged(PropertyTypeCollectionSelector);
}
/// <summary>
@@ -50,7 +44,14 @@ namespace Umbraco.Core.Models
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -60,7 +61,14 @@ namespace Umbraco.Core.Models
public int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
+153 -71
View File
@@ -46,9 +46,9 @@ namespace Umbraco.Core.Models
public PropertyType(IDataTypeDefinition dataTypeDefinition, string propertyTypeAlias)
: this(dataTypeDefinition)
{
_alias = GetAlias(propertyTypeAlias);
SetAlias(propertyTypeAlias);
}
public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType)
: this(propertyEditorAlias, dataTypeDatabaseType, false)
{
@@ -56,7 +56,7 @@ namespace Umbraco.Core.Models
public PropertyType(string propertyEditorAlias, DataTypeDatabaseType dataTypeDatabaseType, string propertyTypeAlias)
: this(propertyEditorAlias, dataTypeDatabaseType, false, propertyTypeAlias)
{
{
}
/// <summary>
@@ -84,25 +84,20 @@ namespace Umbraco.Core.Models
_isExplicitDbType = isExplicitDbType;
_propertyEditorAlias = propertyEditorAlias;
_dataTypeDatabaseType = dataTypeDatabaseType;
_alias = GetAlias(propertyTypeAlias);
SetAlias(propertyTypeAlias);
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.Name);
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.Alias);
public readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.Description);
public readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo<PropertyType, int>(x => x.DataTypeDefinitionId);
public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.PropertyEditorAlias);
public readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo<PropertyType, DataTypeDatabaseType>(x => x.DataTypeDatabaseType);
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<PropertyType, bool>(x => x.Mandatory);
public readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.HelpText);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<PropertyType, int>(x => x.SortOrder);
public readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.ValidationRegExp);
public readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo<PropertyType, Lazy<int>>(x => x.PropertyGroupId);
}
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.Name);
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.Alias);
private static readonly PropertyInfo DescriptionSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.Description);
private static readonly PropertyInfo DataTypeDefinitionIdSelector = ExpressionHelper.GetPropertyInfo<PropertyType, int>(x => x.DataTypeDefinitionId);
private static readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.PropertyEditorAlias);
private static readonly PropertyInfo DataTypeDatabaseTypeSelector = ExpressionHelper.GetPropertyInfo<PropertyType, DataTypeDatabaseType>(x => x.DataTypeDatabaseType);
private static readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<PropertyType, bool>(x => x.Mandatory);
private static readonly PropertyInfo HelpTextSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.HelpText);
private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<PropertyType, int>(x => x.SortOrder);
private static readonly PropertyInfo ValidationRegExpSelector = ExpressionHelper.GetPropertyInfo<PropertyType, string>(x => x.ValidationRegExp);
private static readonly PropertyInfo PropertyGroupIdSelector = ExpressionHelper.GetPropertyInfo<PropertyType, Lazy<int>>(x => x.PropertyGroupId);
/// <summary>
/// Gets of Sets the Name of the PropertyType
@@ -111,7 +106,14 @@ namespace Umbraco.Core.Models
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_name = value;
return _name;
}, _name, NameSelector);
}
}
/// <summary>
@@ -121,7 +123,14 @@ namespace Umbraco.Core.Models
public string Alias
{
get { return _alias; }
set { SetPropertyValueAndDetectChanges(GetAlias(value), ref _alias, Ps.Value.AliasSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
SetAlias(value);
return _alias;
}, _alias, AliasSelector);
}
}
/// <summary>
@@ -131,7 +140,14 @@ namespace Umbraco.Core.Models
public string Description
{
get { return _description; }
set { SetPropertyValueAndDetectChanges(value, ref _description, Ps.Value.DescriptionSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_description = value;
return _description;
}, _description, DescriptionSelector);
}
}
/// <summary>
@@ -142,14 +158,28 @@ namespace Umbraco.Core.Models
public int DataTypeDefinitionId
{
get { return _dataTypeDefinitionId; }
set { SetPropertyValueAndDetectChanges(value, ref _dataTypeDefinitionId, Ps.Value.DataTypeDefinitionIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_dataTypeDefinitionId = value;
return _dataTypeDefinitionId;
}, _dataTypeDefinitionId, DataTypeDefinitionIdSelector);
}
}
[DataMember]
public string PropertyEditorAlias
{
get { return _propertyEditorAlias; }
set { SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_propertyEditorAlias = value;
return _propertyEditorAlias;
}, _propertyEditorAlias, PropertyEditorAliasSelector);
}
}
/// <summary>
@@ -183,7 +213,11 @@ namespace Umbraco.Core.Models
//don't allow setting this if an explicit declaration has been made in the ctor
if (_isExplicitDbType) return;
SetPropertyValueAndDetectChanges(value, ref _dataTypeDatabaseType, Ps.Value.DataTypeDatabaseTypeSelector);
SetPropertyValueAndDetectChanges(o =>
{
_dataTypeDatabaseType = value;
return _dataTypeDatabaseType;
}, _dataTypeDatabaseType, DataTypeDatabaseTypeSelector);
}
}
@@ -195,7 +229,14 @@ namespace Umbraco.Core.Models
internal Lazy<int> PropertyGroupId
{
get { return _propertyGroupId; }
set { SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, Ps.Value.PropertyGroupIdSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_propertyGroupId = value;
return _propertyGroupId;
}, _propertyGroupId, PropertyGroupIdSelector);
}
}
/// <summary>
@@ -205,7 +246,14 @@ namespace Umbraco.Core.Models
public bool Mandatory
{
get { return _mandatory; }
set { SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_mandatory = value;
return _mandatory;
}, _mandatory, MandatorySelector);
}
}
/// <summary>
@@ -216,7 +264,14 @@ namespace Umbraco.Core.Models
public string HelpText
{
get { return _helpText; }
set { SetPropertyValueAndDetectChanges(value, ref _helpText, Ps.Value.HelpTextSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_helpText = value;
return _helpText;
}, _helpText, HelpTextSelector);
}
}
/// <summary>
@@ -226,7 +281,14 @@ namespace Umbraco.Core.Models
public int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_sortOrder = value;
return _sortOrder;
}, _sortOrder, SortOrderSelector);
}
}
/// <summary>
@@ -236,16 +298,23 @@ namespace Umbraco.Core.Models
public string ValidationRegExp
{
get { return _validationRegExp; }
set { SetPropertyValueAndDetectChanges(value, ref _validationRegExp, Ps.Value.ValidationRegExpSelector); }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_validationRegExp = value;
return _validationRegExp;
}, _validationRegExp, ValidationRegExpSelector);
}
}
private string GetAlias(string value)
private void SetAlias(string value)
{
//NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of
// being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix
// which is used internally
return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix)
_alias = value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix)
? value
: value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase);
}
@@ -298,21 +367,18 @@ namespace Umbraco.Core.Models
}
/// <summary>
/// Gets a value indicating whether the value is of the expected type
/// for the property, and can be assigned to the property "as is".
/// Validates the Value from a Property according to its type
/// </summary>
/// <param name="value">The value.</param>
/// <returns>True if the value is of the expected type for the property,
/// and can be assigned to the property "as is". Otherwise, false, to indicate
/// that some conversion is required.</returns>
/// <param name="value"></param>
/// <returns>True if valid, otherwise false</returns>
public bool IsPropertyTypeValid(object value)
{
// null values are assumed to be ok
//Can't validate null values, so just allow it to pass the current validation
if (value == null)
return true;
// check if the type of the value matches the type from the DataType/PropertyEditor
var valueType = value.GetType();
//Check type if the type of the value match the type from the DataType/PropertyEditor
Type type = value.GetType();
//TODO Add PropertyEditor Type validation when its relevant to introduce
/*bool isEditorModel = value is IEditorModel;
@@ -329,34 +395,50 @@ namespace Umbraco.Core.Models
return argument == type;
}*/
if (PropertyEditorAlias.IsNullOrWhiteSpace() == false) // fixme - always true?
if (PropertyEditorAlias.IsNullOrWhiteSpace() == false)
{
// simple validation using the DatabaseType from the DataTypeDefinition
// and the Type of the passed in value
switch (DataTypeDatabaseType)
{
// fixme breaking!
case DataTypeDatabaseType.Integer:
return valueType == typeof(int);
case DataTypeDatabaseType.Decimal:
return valueType == typeof(decimal);
case DataTypeDatabaseType.Date:
return valueType == typeof(DateTime);
case DataTypeDatabaseType.Nvarchar:
return valueType == typeof(string);
case DataTypeDatabaseType.Ntext:
return valueType == typeof(string);
}
//Find DataType by Id
//IDataType dataType = DataTypesResolver.Current.GetById(DataTypeControlId);
//Check if dataType is null (meaning that the ControlId is valid) ?
//Possibly cast to BaseDataType and get the DbType from there (which might not be possible because it lives in umbraco.cms.businesslogic.datatype) ?
//Simple validation using the DatabaseType from the DataTypeDefinition and Type of the passed in value
if (DataTypeDatabaseType == DataTypeDatabaseType.Integer && type == typeof(int))
return true;
if (DataTypeDatabaseType == DataTypeDatabaseType.Decimal && type == typeof(decimal))
return true;
if (DataTypeDatabaseType == DataTypeDatabaseType.Date && type == typeof(DateTime))
return true;
if (DataTypeDatabaseType == DataTypeDatabaseType.Nvarchar && type == typeof(string))
return true;
if (DataTypeDatabaseType == DataTypeDatabaseType.Ntext && type == typeof(string))
return true;
}
// fixme - never reached + makes no sense?
// fallback for simple value types when no Control Id or Database Type is set
if (valueType.IsPrimitive || value is string)
//Fallback for simple value types when no Control Id or Database Type is set
if (type.IsPrimitive || value is string)
return true;
return false;
}
/// <summary>
/// Checks the underlying property editor prevalues to see if the one that allows changing of the database field
/// to which data is saved (dataInt, dataVarchar etc.) is included. If so that means the field could be changed when the data
/// type is saved.
/// </summary>
/// <returns></returns>
internal bool CanHaveDataValueTypeChanged()
{
var propertyEditor = PropertyEditorResolver.Current.GetByAlias(_propertyEditorAlias);
return propertyEditor.PreValueEditor.Fields
.SingleOrDefault(x => x.Key == Constants.PropertyEditors.PreValueKeys.DataValueType) != null;
}
/// <summary>
/// Validates the Value from a Property according to the validation settings
/// </summary>
@@ -376,15 +458,15 @@ namespace Umbraco.Core.Models
var regexPattern = new Regex(ValidationRegExp);
return regexPattern.IsMatch(value.ToString());
}
catch
catch
{
throw new Exception(string .Format("Invalid validation expression on property {0}",this.Alias));
}
}
//TODO: We must ensure that the property value can actually be saved based on the specified database type
//TODO Add PropertyEditor validation when its relevant to introduce
/*if (value is IEditorModel && DataTypeControlId != Guid.Empty)
{
@@ -402,19 +484,19 @@ namespace Umbraco.Core.Models
{
if (base.Equals(other)) return true;
//Check whether the PropertyType's properties are equal.
//Check whether the PropertyType's properties are equal.
return Alias.InvariantEquals(other.Alias);
}
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
//Get hash code for the Name field if it is not null.
int baseHash = base.GetHashCode();
//Get hash code for the Alias field.
//Get hash code for the Alias field.
int hashAlias = Alias.ToLowerInvariant().GetHashCode();
//Calculate the hash code for the product.
//Calculate the hash code for the product.
return baseHash ^ hashAlias;
}
@@ -426,7 +508,7 @@ namespace Umbraco.Core.Models
//need to manually assign the Lazy value as it will not be automatically mapped
if (PropertyGroupId != null)
{
clone._propertyGroupId = new Lazy<int>(() => PropertyGroupId.Value);
clone._propertyGroupId = new Lazy<int>(() => PropertyGroupId.Value);
}
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);

Some files were not shown because too many files have changed in this diff Show More