Running Python Script from C# with IronPython - c#

While I am trying to run python code from C# with Iron Python I get some exceptions tried writing code inside C# but it didn't change anything. Is there any other option to run python code inside C#? or Am I doing something wrong with my code? In python everything works great Until I run it from C#
private static string GetBitcoinPrivateKeyFromMnemonic(string mnemonic)
{
var engine = Python.CreateEngine();
ICollection<string> paths = engine.GetSearchPaths();
var dir1 = #"/home/hackslash/.local/lib/python3.7/site-packages";
var dir2 = #"/usr/lib/python3.7";
paths.Add(dir1);
paths.Add(dir2);
engine.SetSearchPaths(paths);
dynamic py = engine.ExecuteFile(#"/home/hackslash/Magnify/MagnifyDevelopment/Python/bitcoin.py");
dynamic bitcoin = py.Bitcoin();
var bitcoinPrivateWIF = bitcoin.generate_bitcoin_private_wif(mnemonic);
return bitcoinPrivateWIF;
}
and I have this file in Python
from btctools import *
import hashlib
class Bitcoin:
#classmethod
def generate_bitcoin_address_from_wif(cls, private_key_wif):
private_key = PrivateKey.from_wif(private_key_wif)
public_key = private_key.to_public()
public_address = public_key.to_address('P2PKH', compressed=False)
return public_address
#classmethod
def generate_bitcoin_private_wif(cls, mnemonic):
hashed_words = hashlib.sha256(mnemonic.encode()).hexdigest()
private_key = PrivateKey.from_hex(hashed_words)
private_wif = private_key.wif(compressed=False)
return private_wif
I get this Exception:
Unhandled Exception:
Microsoft.Scripting.SyntaxErrorException: invalid syntax
at IronPython.Runtime.ThrowingErrorSink.Add (Microsoft.Scripting.SourceUnit sourceUnit, System.String message, Microsoft.Scripting.SourceSpan span, System.Int32 errorCode, Microsoft.Scripting.Severity severity) [0x0001a] in <0569a20e5dd94f74a766cc11c6214b7c>:0
at IronPython.Compiler.Parser.ReportSyntaxError (System.Int32 start, System.Int32 end, System.String message, System.Int32 errorCode) [0x0003a] in <0569a20e5dd94f74a766cc11c6214b7c>:0
at IronPython.Compiler.Parser.ReportSyntaxError (System.Int32 start, System.Int32 end, System.String message) [0x00000] in <0569a20e5dd94f74a766cc11c6214b7c>:0
at IronPython.Compiler.Parser.ReportSyntaxError (System.String message) [0x00027] in <0569a20e5dd94f74a766cc11c6214b7c>:0
at IronPython.Compiler.Parser.AddTrailers (IronPython.Compiler.Ast.Expression ret, System.Boolean allowGeneratorExpression) [0x00103] in <0569a20e5dd94f74a766cc11c6214b7c>:0

Related

Why is it giving me the 'Access to path denied' error when i try to run my c# code in sublime?

I am trying to run my c# code using ScriptCS in Sublime. I have added ScriptCS(and sublime just in case, since i don't really understand path that well) to PATH. It gives me this error
Copying directory 'packages' to 'scriptcs_packages'...
Unhandled Exception: System.UnauthorizedAccessException: Access to the path 'scriptcs_packages' is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost)
at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost)
at ScriptCs.FileSystem.CopyDirectory(String source, String dest, Boolean overwrite)
at ScriptCs.FileSystemMigrator.Migrate()
at ScriptCs.Command.CommandFactory.CreateCommand(Config config, String[] scriptArgs)
at ScriptCs.Application.Run(Config config, String[] scriptArgs)
at ScriptCs.Program.Main(String[] args)
and if it helps, here is what the file contains. It most probably doesn't contain anything that contributes to the issue i'm facing, but here it is anyway.
using System;
using static System.Console;
namespace main {
class menu {
}
class game {
public void Start() {
WriteLine("Hello World");
ReadKey(true);
}
}
class Program {
static void Main(string args) {
game myGame = new game();
myGame.Start();
}
}
}

NLog Exception on Log to File (Unity project)

I'm using programmatic configuration with NLog and am encountering the following error whenever the log file should be written to:
ArgumentOutOfRangeException: Argument is out of range.
System.Security.AccessControl.AuthorizationRule..ctor
(System.Security.Principal.IdentityReference identity, Int32
accessMask, Boolean isInherited, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags) (at
/Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Security.AccessControl/AuthorizationRule.cs:61)
System.Security.AccessControl.AccessRule..ctor
(System.Security.Principal.IdentityReference identity, Int32
accessMask, Boolean isInherited, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags, AccessControlType type)
System.Security.AccessControl.MutexAccessRule..ctor
(System.Security.Principal.IdentityReference identity, MutexRights
eventRights, AccessControlType type)
NLog.Internal.FileAppenders.BaseFileAppender.CreateSharableMutex
(System.String mutexNamePrefix)
NLog.Internal.FileAppenders.BaseFileAppender.CreateSharableArchiveMutex
()
NLog.Internal.FileAppenders.RetryingMultiProcessFileAppender.CreateArchiveMutex
() NLog.Internal.FileAppenders.BaseFileAppender..ctor (System.String
fileName, ICreateFileParameters createParameters)
NLog.Internal.FileAppenders.RetryingMultiProcessFileAppender..ctor
(System.String fileName, ICreateFileParameters parameters)
NLog.Internal.FileAppenders.RetryingMultiProcessFileAppender+Factory.NLog.Internal.FileAppenders.IFileAppenderFactory.Open
(System.String fileName, ICreateFileParameters parameters)
NLog.Internal.FileAppenders.FileAppenderCache.AllocateAppender
(System.String fileName) NLog.Targets.FileTarget.WriteToFile
(System.String fileName, NLog.LogEventInfo logEvent, System.Byte[]
bytes, Boolean justData) NLog.Targets.FileTarget.ProcessLogEvent
(NLog.LogEventInfo logEvent, System.String fileName, System.Byte[]
bytesToWrite) NLog.Targets.FileTarget.Write (NLog.LogEventInfo
logEvent) NLog.Targets.Target.Write (AsyncLogEventInfo logEvent)
Google doesn't know anything about this error, as far as I can see. The following is my configuration code:
public static void SetupLogging()
{
// Can unity debug be redirected to go through Nlog?
var config = new LoggingConfiguration();
var consoleTarget = new ConsoleTarget("console");
config.AddTarget("console", consoleTarget);
//var logsPath = UtilsIO.GetResourcesPath(UtilsIO.ResourceType.Logs, "_logs");
var logsPath = #"d:\jem\temp\_logs";
var dir = logsPath + "\\app" + "\\" + Environment.UserName;
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var filepath = Path.Combine(dir, Guid.NewGuid() + ".log");
var fileTarget = new FileTarget("file")
{
FileName = filepath,
Layout = "${date:format=yyyyMMddHHmmss} ${message}"
};
config.AddTarget("file", fileTarget);
var rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
config.LoggingRules.Add(rule1);
var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule2);
InternalLogger.LogToConsole = true;
LogManager.ThrowExceptions = true;
LogManager.Configuration = config;
}
This is how I instantiate the logger:
private static NLog.Logger logger = LogManager.GetLogger("file");
And this is how I use that instantiation:
logger.Debug("Hello world????");
I've tried using the ImpersonatingTargetWrapper but get the same error. I've given 'Everyone' full rights to the root directory. I've also tried configuration file to get started, but that didn't help either. I've banged my head for a while against this one - anyone have any suggestions?
NLog 4.4.1 now performs runtime detection of MONO version, and avoids using named mutex if not running MONO ver. 4 or newer (Unity is running MONO ver. 2)

ZipFile.ExtractToDirectory "Illegal characters in path"

I want to unzip a file with ZipFile class in c# (VS2012).
Even if I copy the paths directly from win explorer I get this error:
System.ArgumentException: Illegales Zeichen im Pfad. bei
System.IO.Path.CheckInvalidPathChars(String path, Boolean
checkAdditional) bei System.IO.Path.GetFileName(String path) bei
System.IO.Compression.ZipHelper.EndsWithDirChar(String test) bei
System.IO.Compression.ZipArchiveEntry.set_FullName(String value)
bei System.IO.Compression.ZipArchiveEntry..ctor(ZipArchive archive,
ZipCentralDirectoryFileHeader cd) bei
System.IO.Compression.ZipArchive.ReadCentralDirectory() bei
System.IO.Compression.ZipArchive.get_Entries() bei
System.IO.Compression.ZipFileExtensions.ExtractToDirectory(ZipArchive
source, String destinationDirectoryName) bei
System.IO.Compression.ZipFile.ExtractToDirectory(String
sourceArchiveFileName, String destinationDirectoryName, Encoding
entryNameEncoding) bei
System.IO.Compression.ZipFile.ExtractToDirectory(String
sourceArchiveFileName, String destinationDirectoryName) bei
WindowsFormsApplication1.MainForm.buttonStartNxtOSEK_Click(Object
sender, EventArgs e) in
d:\C#\nxtOSEKInstaller\nxtOSEKSetup\WindowsFormsApplication1\Form1.cs:Zeile
192.
Code:
string zipPath = #"D:\C#\nxtOSEKInstaller\nxtOSEKSetup\WindowsFormsApplication1\bin\Debug\res\package.zip";
string extractPath = #"D:\testcyginstall\cygwin";
textBoxProgress.AppendText("Entpacke .... ");
try {
ZipFile.ExtractToDirectory(zipPath, extractPath);
} catch (System.ArgumentException ex) {
textBoxProgress.AppendText("\n" + "Error\n" + ex.ToString());
return;
}
EDIT
Problem solved: Some files with chinese file names in the zip file caused the problem.
It's very frustrating when the exception does not output the problematic path name.
As you already know some characters are not valid on windows:
\ / : * ? " < > |
This would bring a lot of situations when your application receives zip from different OS since some of those invalid characters are valid in other OS.
In order to solve this problem you can sanitize your files names before you extract them:
public void ExtractZipFileToPath(
string zipFilePath,
string ouputPath
)
{
using (var zip = ZipFile.Read(zipFilePath))
{
foreach (var entry in zip.Entries.ToList())
{
entry.FileName = SanitizeFileName(entry.FileName);
entry.Extract(ouputPath);
}
}
}
Sanitizing examples here How to remove illegal characters from path and filenames?

Binding to Objective C library (VFR Reader)

I'm new to MonoTouch development and I would like to embed some PDF Viewing functionality in my app. I have found several resources for doing that, however, I also see enough remarks about all the additional implementations to make it stable and fast.
I now see that there is a good ObjectiveC library which already implements a lot of functionality (CATiledLayer, multi-threading, page scrolling, thumb nails, device rotation ...): https://github.com/vfr/Reader
The last days, after reading the monotoch binding documentation, I'm trying to bind this in MonoTouch, but without success.
I'm able to export it to a library (.a) file and I've created a binding API.
//#interface ReaderDocument : NSObject <NSObject, NSCoding>
[BaseType (typeof (NSObject))]
interface ReaderDocument {
//- (id)initWithFilePath:(NSString *)fullFilePath password:(NSString *)phrase;
[Export("initWithFilePath:password")]
IntPtr Constructor (string path, string phrase);
//Properties
[Export("guid")]
string Guid { get;}
[Export("fileDate")]
NSDate FileDate { get;}
[Export("lastOpen")]
NSDate LastOpen { get;set;}
[Export("fileSize")]
NSNumber FileSize{ get;}
[Export("pageCount")]
NSNumber PageCount { get;}
[Export("pageNumber")]
NSNumber PageNumber { get;set;}
[Export("bookmarks")]
NSMutableIndexSet Bookmarks { get;}
[Export("fileName")]
string FileName { get;}
[Export("password")]
string Password { get;}
[Export("fileURL")]
NSUrl FileURL { get;}
//Methods
//+ (ReaderDocument *)withDocumentFilePath:(NSString *)filename password:(NSString *)phrase;
[Static, Export("withDocumentFilePath:password")]
ReaderDocument WithDocumentFilePath(string filename, string phrase);
//+ (ReaderDocument *)unarchiveFromFileName:(NSString *)filename password:(NSString *)phrase;
[Static, Export("unarchiveFromFileName:password")]
ReaderDocument UnarchiveFromFileName(string filename, string phrase);
//- (void)saveReaderDocument;
[Export("saveReaderDocument")]
void SaveReaderDocument();
//- (void)updateProperties;
[Export("updateProperties")]
void updateProperties();
}
I'm very unsure about following line btw:
//#interface ReaderDocument : NSObject <NSObject, NSCoding>
[BaseType (typeof (NSObject))]
interface ReaderDocument
Not sure if I have to do something with the ""?
I can now create following code in MonoTouch
ReaderDocument doc = ReaderDocument.withDocumentFilePath("Tamarin.pdf","");
or
ReaderDocument doc = new ReaderDocument("Tamarin.pdf","yrt");
Both are resulting in "unrecognized selector" error
2012-11-04 22:15:05.731 PFDTest1[4149:1507] +[ReaderDocument withDocumentFilePath:password]: unrecognized selector sent to class 0x2f7738
[ERROR] FATAL UNHANDLED EXCEPTION: MonoTouch.Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInvalidArgumentException Reason: +[ReaderDocument withDocumentFilePath:password]: unrecognized selector sent to class 0x2f7738
at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:IntPtr_objc_msgSend_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
at VFRBinding4.ReaderDocument.withDocumentFilePath (System.String filename, System.String phrase) [0x00000] in <filename unknown>:0
at PFDTest1.AppDelegate.FinishedLaunching (MonoTouch.UIKit.UIApplication app, MonoTouch.Foundation.NSDictionary options) [0x00030] in /Users/matthiasvalcke/Projects/PFDTest1/PFDTest1/AppDelegate.cs:39
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at PFDTest1.Application.Main (System.String[] args) [0x00000] in /Users/matthiasvalcke/Projects/PFDTest1/PFDTest1/Main.cs:17
Any ideas?
There could be other issues but your bindings are wrong for the constructors, i.e.
//- (id)initWithFilePath:(NSString *)fullFilePath password:(NSString *)phrase;
[Export("initWithFilePath:password")]
void InitWithFilePath(string path, string password);
ObjectiveC init* selectors should be binded as C# constructors. E.g.
[Export("initWithFilePath:password")]
IntPtr Constructor (string path, string password);
and that should be what you use to create the instance, e.g.
ReaderDocument doc = new ReaderDocument ("sample.pdf", "");
// ...
I could be entirely wrong, but I think your selectors are wrong:
e.g. "withDocumentFilePath:password" should be "withDocumentFilePath:password:"

Intermittent errors while de-serializing object from XML

I have a program that takes objects stored as XML in a database (basicly a message queue) and de-serializes them. Intermittently, I will get one of the following errors:
System.Runtime.InteropServices.ExternalException: Cannot execute a program. The command being executed was "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe" /noconfig /fullpaths #"C:\Documents and Settings\useraccount\Local Settings\Temp\lh21vp3m.cmdline".
at System.CodeDom.Compiler.Executor.ExecWaitWithCaptureUnimpersonated(SafeUserTokenHandle userToken, String cmd, String currentDir, TempFileCollection tempFiles, String& outputName, String& errorName, String trueCmdLine)
at System.CodeDom.Compiler.Executor.ExecWaitWithCapture(SafeUserTokenHandle userToken, String cmd, String currentDir, TempFileCollection tempFiles, String& outputName, String& errorName, String trueCmdLine)
at Microsoft.CSharp.CSharpCodeGenerator.Compile(CompilerParameters options, String compilerDirectory, String compilerExe, String arguments, String& outputFile, Int32& nativeReturnValue, String trueArgs)
at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch(CompilerParameters options, String[] fileNames)
at Microsoft.CSharp.CSharpCodeGenerator.FromSourceBatch(CompilerParameters options, String[] sources)
at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, String[] sources)
at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource(CompilerParameters options, String[] sources)
at System.Xml.Serialization.Compiler.Compile(Assembly parent, String ns, XmlSerializerCompilerParameters xmlParameters, Evidence evidence)
at System.Xml.Serialization.TempAssembly.GenerateAssembly(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, Evidence evidence, XmlSerializerCompilerParameters parameters, Assembly assembly, Hashtable assemblies)
at System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, String location, Evidence evidence)
at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlMapping xmlMapping, Type type, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type)
.....
Or I'll get this one:
System.InvalidOperationException: Unable to generate a temporary class (result=1).
error CS0016: Could not write to output file 'c:\Documents and Settings\useraccount\Local Settings\Temp\nciktsd7.dll' -- 'Could not execute CVTRES.EXE.'
at System.Xml.Serialization.Compiler.Compile(Assembly parent, String ns, XmlSerializerCompilerParameters xmlParameters, Evidence evidence)
at System.Xml.Serialization.TempAssembly.GenerateAssembly(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, Evidence evidence, XmlSerializerCompilerParameters parameters, Assembly assembly, Hashtable assemblies)
at System.Xml.Serialization.TempAssembly..ctor(XmlMapping[] xmlMappings, Type[] types, String defaultNamespace, String location, Evidence evidence)
at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(XmlMapping xmlMapping, Type type, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
at System.Xml.Serialization.XmlSerializer..ctor(Type type)
....
The program process thousands of messages a day successfully, but I only get these errors maybe 2 or 3 times a day. They don't appear to be correlated to any specific kind of message, just completely random.
Any idea what causes those errors and how to fix it?
ETA - Here's the code that is causing the errors, in case that helps:
public class MessageContextBuilder<T> where T : MessageContextBase
{
private static IDictionary<string, XmlSerializer> SerializerCache { get; set; }
public ILog Logger { get; set; }
public MessageContextBuilder() {
if (SerializerCache == null) SerializerCache = new Dictionary<string, XmlSerializer>();
Logger = LogContextManager.Context.GetLogger<MessageContextBuilder<T>>();
}
public T BuildContextFromMessage(IEmailQueueMessage msg) {
XmlSerializer serializer = GetSerializer(typeof(T));
XmlReader r = XmlReader.Create(new StringReader(msg.MessageDetails));
if (serializer.CanDeserialize(r)) {
T rval = (T)serializer.Deserialize(r);
rval.EmailAddress = msg.EmailAddress;
rval.LocaleID = msg.LocaleID;
rval.StoreID = msg.StoreID;
rval.MessageID = msg.UniqueKey;
return rval;
} else {
throw new ArgumentException("Cannot deserialize XML in message details for message #" + msg.UniqueKey);
}
}
public XmlSerializer GetSerializer(Type t) {
if (!SerializerCache.ContainsKey(t.FullName)) {
SerializerCache.Add(t.FullName, new XmlSerializer(t)); // Error occurs here, in XmlSerializer constructor, intermittently
}
return SerializerCache[t.FullName];
}
}
You can pre-create serializers: http://msdn.microsoft.com/en-us/library/bk3w6240%28v=VS.100%29.aspx Just give it a try. The next canonical candidate for such problems is your virus scanner. Your tool is writing to disc while creating serializers. I have seen virus scanner producing all kind of strange errors in such situations.
XmlSerializer is supposed to be thread safe.
Even if that's the case, you can notice the behavior you are getting is in both cases failing at: XmlSerializer..ctor(Type type)
Given that, it seriously look like a multi-threading limitation trying to create serializers.
I suggest to take this code you have:
public XmlSerializer GetSerializer(Type t) {
if (!SerializerCache.ContainsKey(t.FullName)) {
SerializerCache.Add(t.FullName, new XmlSerializer(t)); // Error occurs here, intermittently
}
return SerializerCache[t.FullName];
}
And implement a lock on the Add. This way you are only creating 1 serializer at a time. The hit is small if you aren't processing tons of different types.
Note that you need the lock anyway, as the way it is you could get duplicate exceptions when 2 types try to be added at the same time.
static object serializerCacheLock = new object();
public XmlSerializer GetSerializer(Type t) {
if (!SerializerCache.ContainsKey(t.FullName))
lock(serializerCacheLock)
if (!SerializerCache.ContainsKey(t.FullName)) {
SerializerCache.Add(t.FullName, new XmlSerializer(t));
}
return SerializerCache[t.FullName];
}
If the above still isn't enough, I'd try with a read/write lock on serializer constructor vs. serializers usage. Line of thought being that maybe the multi-threading issue is worth than 2 ctors running at the same time.
All above is a Huge guess, but if it were me I'd definitely confirm is not that.
For the first error (cannot execute a program), you might be running into the same XmlSerializer bug that we ran into. It turns out XmlSerlializer throws that exception when Directory.CurrentDirectory is set to a folder that no longer exists.
Our specific situation is different than yours, but I'll give the details in case it helps shed light on what might be happening for you, or it helps anyone else. In our case, a small number of our customers would get that error after launching our WinForms application directly from the installer, i.e. they chose the "run now" option after installing or upgrading. (Unclear why it happened to some but not others). What we suspect is happening is that our installer (InstallAware) occasionally starts our application with the current directory set to a folder that no longer exists, or is about to be deleted. To test this theory, I wrote a test app which simulates launching from the installer:
string dir = #"C:\Users\me\Documents\Temp\WillBeDeleted";
Directory.CreateDirectory(dir);
Directory.SetCurrentDirectory(dir);
Process.Start(#"C:\Program Files (x86)\...\our.exe");
Directory.SetCurrentDirectory(#"C:\"); // otherwise, won't be able to delete
Directory.Delete(dir);
Sure enough, as soon as the launched application created a new instance of XmlSerializer, the exception would be thrown. I put in trace statements to show the result of GetCurrentDirectory(), and indeed it was set to the WillBeDeleted folder. The fix was to SetCurrentDirectory to a valid location during application initialization, before any serialization took place.
This is a sign that you are not caching your serialisers which is not good at all => it leads to memory leak and I suspect you will experience this.
Remember that .NET generates code and compiles them into assemblies every time you create a serialiser.
Always create your serialisers and then cache them.
Here is a sample:
public class SerialiserCache
{
private static readonly SerialiserCache _current = new SerialiserCache();
private Dictionary<Type, XmlSerializer> _cache = new Dictionary<Type, XmlSerializer>();
private SerialiserCache()
{
}
public static SerialiserCache Current
{
get { return _current; }
}
public XmlSerializer this[Type t]
{
get
{
LoadIfNecessary(t);
return _cache[t];
}
}
private void LoadIfNecessary(Type t)
{
// double if to prevent race conditions
if (!_cache.ContainsKey(t))
{
lock (_cache)
{
if (!_cache.ContainsKey(t))
{
_cache[t] = new XmlSerializer(typeof(T));
}
}
}
}
}

Categories

Resources