We are trying to move a file to an FTP site. We need to create a folder structure like this:
/deeply/nested/folder/structure/index.html
Yesterday, we realized we can't create more than one folder at a time, so this doesn't work:
MKD /deeply/nested/folder/structure
So, in code, we wrote a loop that created each folder, one at a time, ignoring errors caused by the folder already existing. Ignoring errors is gross.
Is a way to create these nested folders in one action, rather than multiple? Is there a command to see if a folder already exists? If we just push the file out including the full path, will FTP be smart enough to create the directories for me?
No, there's no standard way to create a folder with subfolders. There's also no standard way to check if directory exists. You would need to use LIST or MLSD (where supported) and parse the result for this. Can't you just use some third-party component that supports the needed functionality?
I wrote a simple C# example here, maybe someone will need this code.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
namespace Deployer
{
public class RecursiveFolderUploader
{
private const string SrcFullPath = #"C:\prg\Snowinmars\Snowinmars.Ui";
private readonly IList<string> foldersOnServer;
private Client ftpClient; // that's your ftp client, init it
public RecursiveFolderUploader()
{
this.foldersOnServer = new List<string>();
}
private void UploadAll(DirectoryInfo directoryInfo)
{
// ftp://login:password#127.0.0.1/path/to/root/mydir/anotherdir/file.dat
// ^________________uri_______________________^_relevationPath_^
foreach (var file in directoryInfo.EnumerateFiles())
{
if (!file.Directory.FullName.StartsWith(RecursiveFolderUploader.SrcFullPath))
{
throw new InvalidOperationException($"File {file.FullName} is not from {RecursiveFolderUploader.SrcFullPath} folder");
}
string relevationPath; // all folders from root up to file
if (file.Directory.FullName.Length == RecursiveFolderUploader.SrcFullPath.Length)
{
relevationPath = "";
}
else
{
relevationPath = file.Directory.FullName.Substring(RecursiveFolderUploader.SrcFullPath.Length, file.Directory.FullName.Length - RecursiveFolderUploader.SrcFullPath.Length);
if (relevationPath.StartsWith("\\"))
{
relevationPath = relevationPath.Remove(0, 1);
}
}
string destination;
if (string.IsNullOrWhiteSpace(relevationPath))
{
destination = file.Name;
}
else
{
destination = Path.Combine(relevationPath, file.Name).Replace("\\", "/");
}
try
{
ftpClient.UploadFile(file.FullName, destination);
}
catch (WebException e)
{
// that means that there's no such folder or something else goes wrong
// we can check it by creating folders and try again
var parts = relevationPath.Replace("\\", "/").Split('/');
for (int i = 1; i <= parts.Length; i++)
{
var path = string.Join("/", parts.Take(i));
if (!foldersOnServer.Contains(path))
{
ftpClient.MakeDirectory(path);
foldersOnServer.Add(path);
}
}
try
{
ftpClient.UploadFile(file.FullName, destination);
}
catch (Exception innerE)
{
// if it doesn't help - trouble isn't in folders
throw new WebException($"Can't find folder {relevationPath}", innerE);
}
}
}
foreach (var directory in directoryInfo.EnumerateDirectories())
{
UploadAll(directory);
}
}
}
}
Related
I wanted to easily turn some json schema files into classes. So googling I found NJsonSchema and I implemented this in a visual studio custom tool so I can set this on relevant json files and get my classes out. This al works and I pasted the code below. This code comes from this very answer. Though it does need a little updating for VS2022.
I find that documentation on how to do this is rather rare and the thing I am missing is how I can add something like configuration options for the custom tool.
Take for example the line "ClassStyle = CSharpClassStyle.Record," that is something one might want configurable for the user. But I cannot find anything on how to do that. Anyone have a good pointer/answer on this?
Preferably a way to add take the config from some custom properties in the file its properties that are available when the custom tool is configured on a project file.
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System.Text;
using NJsonSchema.CodeGeneration.CSharp;
using NJsonSchema;
namespace ClassGeneratorForJsonSchema
{
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("GenerateClassesFromJsonSchema", "Use NJsonSchema to generate code from a json schema.", "1.0")]
[Guid("202E7E8B-557E-46CB-8A1D-3024AD68F44A")]
[ComVisible(true)]
[ProvideObject(typeof(ClassGeneratorForJsonSchema))]
[CodeGeneratorRegistration(typeof(ClassGeneratorForJsonSchema), "ClassGeneratorForJsonSchema", "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}", GeneratesDesignTimeSource = true)]
public sealed class ClassGeneratorForJsonSchema : IVsSingleFileGenerator
{
#region IVsSingleFileGenerator Members
public int DefaultExtension(out string pbstrDefaultExtension)
{
pbstrDefaultExtension = ".cs";
return pbstrDefaultExtension.Length;
}
public int Generate(string wszInputFilePath, string bstrInputFileContents,
string wszDefaultNamespace, IntPtr[] rgbOutputFileContents,
out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
{
try
{
var schema = JsonSchema.FromJsonAsync(bstrInputFileContents).Result;
var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings()
{
JsonLibrary = CSharpJsonLibrary.SystemTextJson,
ClassStyle = CSharpClassStyle.Record,
Namespace = wszDefaultNamespace
});
byte[] bytes = Encoding.UTF8.GetBytes(generator.GenerateFile());
int length = bytes.Length;
rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(length);
Marshal.Copy(bytes, 0, rgbOutputFileContents[0], length);
pcbOutput = (uint)length;
}
catch (Exception ex)
{
pcbOutput = 0;
}
return VSConstants.S_OK;
}
#endregion
}
}
this is my function to update an instance but here i can't delete the old image it is only delete the instance from the database but the real image doesn't deleted from the image folder i have got this error message
The instance of entity type 'Images' cannot be tracked because another instance with the same key value for {'ImageId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
public async Task<IActionResult> Edit( int id, [Bind("ImageId,Title,ImageFile")] Images images )
{
if (id != images.ImageId)
{
return NotFound();
}
if (ModelState.IsValid)
{
var im = await _context.Images.FindAsync(id);
//delete image from wwwroot/image
var imagePath = Path.Combine(_hostEnvironment.WebRootPath, "image", im.ImageName);
if (System.IO.File.Exists(imagePath))
System.IO.File.Delete(imagePath);
//save image to wwwroot/image
string wwwRootPath = _hostEnvironment.WebRootPath;
string fileName = Path.GetFileNameWithoutExtension(images.ImageFile.FileName);
string extension = Path.GetExtension(images.ImageFile.FileName);
images.ImageName = fileName = fileName + DateTime.Now.ToString("yymmssfff") + extension;
string path = Path.Combine(wwwRootPath + "/Image", fileName);
using (var fileStream = new FileStream(path, FileMode.Create))
{
await images.ImageFile.CopyToAsync(fileStream);
}
//Edit record
try
{
_context.Update(images);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ImagesExists(images.ImageId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(images);
}
If you want to get the ImageName only then you should use your find method withAsNoTracking(). Probably you need to change your find method on something like that :
var im = await _context.Images.AsNoTracking().SingleOrDefaultAsync(i => i.Id == id)
About AsNoTracking
It doesn't look like you programmed your action to delete the old image... Deleting the image name from the database doesn't magically delete it from your wwwroot (assuming that's where the image is saved).
You can delete an image programmatically if you have its path like this:
System.IO.File.Delete(path_of_image);
But telling from your code, it doesn't look like you have the path of the image you want to delete. To get the path, I recommend doing either one of these two (if applicable):
Adding another property in your view model for the old image path
Querying the database for the old image path before updating the record
I tried making an addon using the existing selenium addon codes and resources.
I was able to make an addon with just one command (for testing) to open Flipkart.
I used the selenium.open command code and edited it slightly by entering default value of URL argument as (flipkart.com).
I was successfully able to build my solution (I made sure to add all the nuget packages and other necessities)
Now when I try to load the addon in my studio, I'm getting an error mentioning that it expected command postfix for the FlipkartOpen command.
Can anyone please let me know the reason for this error and maybe a possible solution to fix it?
Here's the error image: G1ANT Studio Error for New Addon.
And here's my code sample:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using System.Text;
using G1ANT.Language;
using OpenQA.Selenium;
namespace G1ANT.Addon.Flipkart.Commands
{
[Command(Name = "Flipkart.Open", Tooltip = "This command opens flipkart in a web browser provided in the Argument.")]
public class FlipkartOpen : Command
{
public FlipkartOpen(AbstractScripter scripter) : base(scripter)
{
}
public class Arguments : CommandArguments
{
// Enter all arguments you need
[Argument(Required = true, Tooltip = "Name of a web browser")]
public TextStructure Type { get; set; } = new TextStructure(string.Empty);
[Argument(DefaultVariable ="Url", Tooltip = "Website Url")]
public TextStructure Url { get; set; } = new TextStructure("www.flipkart.com");
[Argument(DefaultVariable = "timeoutselenium", Tooltip = "Specifies time in milliseconds for G1ANT.Robot to wait for the command to be executed")]
public override TimeSpanStructure Timeout { get; set; } = new TimeSpanStructure(SeleniumSettings.SeleniumTimeout);
[Argument(Tooltip = "By default, waits until the webpage fully loads")]
public BooleanStructure NoWait { get; set; } = new BooleanStructure(false);
[Argument(Tooltip = "Result variable")]
public VariableStructure Result { get; set; } = new VariableStructure("result");
}
// Implement this method
public void Execute(Arguments arguments)
{
try
{
SeleniumWrapper wrapper = SeleniumManager.CreateWrapper(
arguments.Type.Value,
arguments.Url?.Value,
arguments.Timeout.Value,
arguments.NoWait.Value,
Scripter.Log,
Scripter.Settings.UserDocsAddonFolder.FullName);
int wrapperId = wrapper.Id;
OnScriptEnd = () =>
{
SeleniumManager.DisposeAllOpenedDrivers();
SeleniumManager.RemoveWrapper(wrapperId);
SeleniumManager.CleanUp();
};
Scripter.Variables.SetVariableValue(arguments.Result.Value, new IntegerStructure(wrapper.Id));
}
catch (DriverServiceNotFoundException ex)
{
throw new ApplicationException("Driver not found", ex);
}
catch (Exception ex)
{
throw new ApplicationException($"Error occured while opening new selenium instance. Url '{arguments.Url.Value}'. Message: {ex.Message}", ex);
}
}
}
}
To remove this error, when you add new class, write Command.cs at the end while adding. Try FlipkartopenCommand.cs
This should remove your error.
I'm confused about how should I do a model that has some uploaded file, like for exemplo:
User has photos.
I already found out how to upload a file, but the question here is about what to do with the file that was now uploaded, how can I link this new uploaded file(photo in the exemple) with a model(the user in the example).
Thanks.
OBS: Using play for Java here, not Scala.
You have to link your User to his picture. For that, your best option is to use the User id, which should be unique.
Then, if you uploaded your photo under the pictures/user folder in your filesystem, then you should save the picture as pictures/user/USER_ID.png (png or jpg or anything else).
Then, you can have an action which retrieve the picture according to the user id:
public static Result picture(String userId) {
Picture picture = Picture.findPicture(userId);
if (picture != null) {
response().setContentType(picture.contentType);
return ok(picture.bytes);
}
return notFound();
}
And the Picture class looks like:
public class Picture {
public byte[] bytes;
public String contentType;
public static Picture findPicture(String userId) {
String[] extensions = {"png","jpg"}; // an enum should be better
for (String extension:extensions) {
String path = "pictures/user/" + userId + "." + extension;
if (new File().exists(path)) {
Picture picture = new Picture();
picture.bytes = IOUtils.toByteArray(new FileInpustream(path));
picture.contentType = findContentType(extension);
return picture;
}
}
return null;
}
protected static String findContentType(String extension) {
if (extension.equalsIgnoreCase("jpg") {
return "image/jpeg";
} else if (extension.equalsIgnoreCase("png") {
return "image/png";
}
}
}
I did something similar once (but the pictures were stored in memory), you can take a look here.
Just create a convention if user has only one picture. Per instance, if your user was registered in 2012-07-23 and has id = 100, move the file to some place mapped from these data:
/uploaded-dir/2012/07/23/100/picture.jpg
After that, you can use the same convention to read the file.
I am struggling to get solution folder names in the TFS drop locations.
I have a .NET solution file in the following hirerchy:
Solution File
--> Solution Folder A --> Project A
--> Solution Folder B --> Project B & Project C
Now I want my drop location should be customized like the above project hierarchy.
means : in the Drop Location it should be : SolutionName -> SolutionFolderName -> ProjectName -> {build output}
By implementing customize output directory in my build template , i.e. $(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName), I am getting Solution Name -> Project Name -> Build outputs..
But I am not getting Solution Folder names to structure my outputs. Can anybody suggest how to get this so that I can read from my project files.
Thanks in advance.
You could set a property in the projects that corresponds to the solution folder name (e.g. $(SolutionFolder)). Then you could set the output directory to $(TeamBuildOutDir)\$(SolutionFolder).
public static TeamProject[] GetAllProjects(TfsTeamProjectCollection prjCollection)
{
var versionControl = prjCollection.GetService<VersionControlServer>();
return versionControl.GetAllTeamProjects(true);
}
public static ProjectCollection GetAllIterations(TfsTeamProjectCollection prjCollection)
{
var wiStore = prjCollection.GetService<WorkItemStore>();
return wiStore.Projects;
}
/// <summary>
/// function to get all system project name
/// </summary>
private void IterateFolder()
{
try
{ var selectedProject = "EMRConversion";
Project detailsOfTheSelectedProject = null;
var projCollections = GetAllIterations(prjCollection);
foreach (Project project in projCollections)
{
if (!String.IsNullOrEmpty(selectedProject))
{
if (project.Name.ToString().Contains(selectedProject))
{
detailsOfTheSelectedProject = project;
break;
}
}
}
if (detailsOfTheSelectedProject != null)
{
cmbSystemName.Items.Clear();
foreach (Node area in detailsOfTheSelectedProject.AreaRootNodes)
{
if (!(area.HasChildNodes))
{
cmbSystemName.Items.Add(area.Name);
}
foreach (Node item in area.ChildNodes)
{
cmbSystemName.Items.Add(item.Name);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}