Parsing Ftp.ListDirectoryDetails and ListDirectory

I just started a project which requires a FTP client that will download a root directory recursively.  This means simply that a user can specify a single directory, and this client needs to download all the files in that directory, and traverse through all subdirectories and grab those files too.  To traverse through subdirectories, you have to parse out the directory name from the ListDirectoryDetails output and build a new path for your FtpWebRequest instance.  While attempting to parse the output I ran into an interesting problem.  I construct a ftp request, set the method to ListDirectoryDetails, construct a response object, and read through the response.

There are two possible output formats in my scenario, UNIX style and Windows Style.

UNIX Style:

ftp_Unix

Windows Style:

ftp_windows 

As you can see the output is not delimited using a standard spacing (like tab) or character.  The best way to tell if a record is a directory is not is to look at the first character in the permissions (if it is “d” its a directory).  Using the far left permissions column is better than checking the record for a filename with a “.extension.”  So why not do a String.Split and chop up the line into chunks? Any directory that contains a space will not be parsed correctly. 

After doing some research, I came across a couple of links which talked about using regex to parse out UNIX and windows style ftp dir output.  In my experience regex is expensive.  How do you solve this?  Why not submit two ftpwebrequests? One where method = ListDirectory so you can fetch the name of the file/directory in question (including whitespaces), and then one where method = ListDirectoryDetails so you can see which records are actually directories?

Yes, it’s chatty.. but can handle a variety of common issues, easily.  Note the directory with several spaces:

ftp_fixed

Proof of concept code:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using System.Net;
   5: using System.IO;
   6: using System.Collections;
   7:  
   8: namespace ftpDirTest
   9: {
  10:     class Program
  11:     {
  12:         static void Main(string[] args)
  13:         {
  14:             //Get Directory Details
  15:             FtpWebRequest _lsDirFtp;
  16:             _lsDirFtp = (FtpWebRequest)FtpWebRequest.Create("ftp://ftp.cindercube.org/public_html/cindercube/wp-content/themes/");
  17:             _lsDirFtp.Credentials = new NetworkCredential("someuser", "somepassword");
  18:             _lsDirFtp.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
  19:  
  20:             WebResponse response = _lsDirFtp.GetResponse();
  21:             StreamReader reader = new StreamReader(response.GetResponseStream());
  22:  
  23:             //Get Directory/File names
  24:             FtpWebRequest _lsDirDetailsFtp;
  25:             _lsDirDetailsFtp = (FtpWebRequest)FtpWebRequest.Create("ftp://ftp.cindercube.org/public_html/cindercube/wp-content/themes/");
  26:             _lsDirDetailsFtp.Credentials = new NetworkCredential("someuser", "somepassword");
  27:             _lsDirDetailsFtp.Method = WebRequestMethods.Ftp.ListDirectory;
  28:  
  29:             WebResponse response2 = _lsDirDetailsFtp.GetResponse();
  30:             StreamReader reader2 = new StreamReader(response2.GetResponseStream());
  31:  
  32:             //read file/directory names into arraylist
  33:             string lsdirectory = reader2.ReadLine();
  34:             ArrayList lsnames = new ArrayList();
  35:             while (lsdirectory != null)
  36:             {
  37:                 lsnames.Add(lsdirectory);
  38:                 lsdirectory = reader2.ReadLine();
  39:             }
  40:  
  41:             //read through directory details response
  42:                 string line = reader.ReadLine();
  43:                 while (line != null)
  44:                 {
  45:                     if (line.StartsWith("d") && !line.EndsWith(".")) //"d" = dir don't need "." or ".." dirs
  46:                     {
  47:                            foreach (String chk in lsnames) //compare basic dir output to detail dir output to get dir name
  48:                             {
  49:                                 if (line.EndsWith(chk))
  50:                                 {
  51:                                     //found dir
  52:                                     Console.WriteLine(chk);
  53:                                 }
  54:                             }
  55:                     }
  56:                     line = reader.ReadLine();
  57:                 }
  58:         }
  59:     }
  60: }

Continue reading » · Rating: · Written on: 12-25-07 · 5 Comments »

Recreating Production Environments for Integration and Staging

Fileserver: dir /s /b \*. > dirstructure.txt

lists fullpaths to each directory

Continue reading » · Rating: · Written on: 12-21-07 · No Comments »

SSIS Expressions are great

Previously I posted about how to obtain relative picture paths from your database, flip the direction of the “slashes” and delete the image using a ‘Filesystem Task.’  I posted a stored procedure that does a ‘replace’ on forward slashes with backslashes, and I don’t think that is the best way to do it.  The database should have the least amount of work to do period.  Expressions are a way to evaluate and calculate certain values on the fly.  That being said, use expressions.

Example Scenario:  You have a stored proc that returns a result set of relative paths (i.e. /images/yourface.jpg”).  You want to turn that path into the full UNC path.

 

Step 1: Create a variable to store the returned path

  • In the variables pane click Add Variable (call it ReturnedPath) as a String
  • Create an other variable called BaseDir as a String and hard code the value for your base directory (i.e. \\fileserver\uploads)
  • Create an other variable called FullPath as a String

Step 2: Create an expression for the FullPath variable

  • Click on the variable FullPath and select EvaluateAsExpression to True
  • Click and expand the Expression Property
  • Expression should be: @[User::BaseDir] +   REPLACE( @[User::ReturnedPath] ,”/”, “\\” )

What this does:  It takes the hard coded value of BaseDir and concatenates the ReturnedPath with the forward slashes replaced with backslashes

SSIS_Expressions_pic1

Continue reading » · Rating: · Written on: 12-17-07 · No Comments »

Simple Fluid/Liquid Layout in CSS

I’m always frustrated with CSS, not because of syntax or construction… but the inconsistencies of rendering engines.  Fluidity is important to me, my page should have the same look no matter how you resize your browser window.  Creating fluid/liquid layouts isn’t as hard as I thought it would be.
Step 1:
Draw a general layout of your page on  a piece of paper or in Photoshop, focusing on a very simple layout:
-Footer
-Header
-Column 1
-Column 2
Step 2:
Attach a pixel value to each element using 800px as a base line for the entire width of the page
Step 3:
Take each of the element’s pixel values and calculate their width’s in percentage (if column 1 is 570px, then its percentage is 570px divided by 800px) 
 csslayout_2
Step 4:
Write CSS that uses the values calculated in Step 3

   1: #page 
   2: { 
   3:     width:80%; 
   4:     margin:0 auto 0 auto; 
   5: } 
   6:  
   7: #header
   8: {
   9:     width:100%;
  10:     height:50px;
  11:     margin-bottom:5px;
  12:     background-color:silver;
  13: }
  14:  
  15:  
  16: #col1 
  17: { 
  18:     float:left; 
  19:     width: 71%; 
  20:     margin-left:1%;
  21:     margin-bottom:5px; 
  22:     display:inline; 
  23:     background-color:gray;
  24: } 
  25:  
  26: #col2 
  27: { 
  28:     float:left; 
  29:     width: 20%; 
  30:     margin-left:1%; 
  31:     margin-bottom:5px; 
  32:     background-color:gray;
  33: } 
  34:  
  35: #footer
  36: {
  37:  
  38:     clear:both;
  39:     background-color:silver;
  40: }
  41:  
  42: #gutter 
  43: { 
  44:     float: left; 
  45:     width: 1%; 
  46:     height: 5px; 
  47: } 

Step 5:

Write html to use the CSS created in Step 4

   1: <html>
   2: <head>
   3:   <title>css layouttitle>
   4:     <LINK REL=StyleSheet HREF="style.css" TYPE="text/css">
   5: head>
   6:  
   7: <body>
   8:   <div id="page">
   9:     <div id="header">headerdiv>
  10:         <div id="gutter">div>
  11:     <div id="col1">column 1 contentdiv>
  12:     <div id="col2">column 2 contentdiv>
  13:     <div id="footer">footerdiv>
  14: div>
  15: body>
  16: html>
Continue reading » · Rating: · Written on: 12-16-07 · No Comments »

Deleting Files with SSIS

A common practice is to have user’s upload images to a directory that is readable from IIS, store that upload path to a database, and then construct a relative path to that upload directory for use within your web application.  What happens if that data has to be removed?

Example Scenario: You are running your own version of a social networking web application which users can upload photos of themselves and save it in a profile.  That user wishes to remove themselves from your social networking site and you must delete all database records pertaining to them as well as all the pictures (binaries) that they have uploaded.

Possible Solutions:

  1. Pull the full path for all the binaries from your database and create a batch/vb script to delete the picture files.
  2. Leave the images there, because after the record containing that image’s path is deleted it will never be used (bad idea, you would be eating up un-necessary space).
  3. Create a SSIS package to pull the relative path from the database, construct a UNC or local path and delete the picture files

 

Creating an SSIS package to remove files:

Step 1: Creating the stored procedure 

You need to create a stored procedure to retrieve all of the records that contain some sort of path to where the image is stored.  Quite frequently this path is inserted into the database table as a path that is relative to your IIS instance.  The stored procedure below allows you to supply a user id (named @pUserID) and returns all records in the UserPictures table for that particular user id. Notice that I am flipping the direction of slashes, why do you ask?  Since relative paths are commonly stored for image paths they look like /images/someotherfolder/somephoto.jpg, if we are to delete pictures that are on the local path or UNC path we need to flip the direction of the slashes.

   1: CREATE Procedure GetImagesByUserID
   2: @pUserID int
   3:  
   4: AS
   5: BEGIN
   6:  
   7: SELECT replace(RelativePath,'/','\')
   8: FROM UserPictures
   9: WHERE UserID=@pUserID
  10: END


Step 2: Create a new SSIS package with variables

Although you should keep the number of variables in your package to a minimum, create the following variables:

UserID has the value of ‘911′ as a piece of sample data you will pass into the @pUserID later on

ImageRecords is of data type ‘Object’ because it will hold the resulting record set from the stored proc created in Step 1.

 SSIS_FS_ImageDeletion_step2

Step 3: Retrieve image paths using ‘Execute SQL Task”

This task will supply the UserID variable to our stored proc and execute it. 

 SSIS_FS_ImageDeletion_step3a

Configure the ‘Execute SQL Task’ to:

  • Return a Full Result Set
  • Use ADO.NET as the ConnectionType
  • Select your connection
  • Write the SQL statement to execute the stored proc

 SSIS_FS_ImageDeletion_step3b

Click on the Parameter Mapping tab and click “Add”

Configure:

  • Variable Name is User::UserID
  • Direction is Input
  • Data Type is Int32
  • Parameter Name is @pUserID
  • Parameter Size does not matter

SSIS_FS_ImageDeletion_step3ab

Click on the Result Set section and click “Add” and set the Result Name to ‘0′ and the Variable Name to User::ImageRecords, this will assign the output from the stored proc to your ImageRecords variable.

 SSIS_FS_ImageDeletion_step3c


Step 4: Loop through each record returned using a ‘Foreach Loop Container’

Use a Foreach Loop Container to iterate through the resulting record set from the step above. *Its not generally a good idea to iterate through every record of a record set (especially with thousands of records), but in this case it is very useful.

SSIS_FS_ImageDeletion_step4

Click on the newly created Foreach Loop Container and create a new variable called CurrentFile, this will provide a variable for the ‘Foreach Loop Container’ to assign the image path to.

  • CurrentFIle is a variable for the ‘Foreach Loop Container’ to assign the image path to
  • BaseDir is a variable to which you assign a UNC or local path name (\\myfileserver\mywebapp\, C:\Data)
  • FullPath is a variable which builds the full path for the image

SSIS_FS_ImageDeletion_step4a

Click on the FullPath variable and go to the Properties window

Configure:

  • EvaulateAsExpression is True
  • Expression is @[User::BaseDir] + @[User::CurrentFile]

SSIS_FS_ImageDeletion_step4ab

Double click on the ‘Foreach Loop Container’ and click on the Collection tab and configure:

  • Enumerator is a Foreach ADO Enumerator
  • ADO object source variable is User::ImageRecords this will tell the loop to iterate through the records in the ImageRecords variable.

SSIS_FS_ImageDeletion_step4b

Click the Variable Mappings tab and configure:

  • Variable to “User::CurrentFile”
  • Index to ‘0′

This assigns the value of cell ‘0′ of the current record to CurrentFile

SSIS_FS_ImageDeletion_step4c


Step 5: Delete the file using File System Task

Drag a new ‘File System Task’ into your Foreach Loop Container and double click it.

SSIS_FS_ImageDeletion_step5a

Configure:

  • Operation is Delete File
  • IsSourcePathVariable is True
  • SourceVariable is User::FullPath

SSIS_FS_ImageDeletion_step5b 

Your final package should look something like this:

SSIS_FS_ImageDeletion_step5c


*Notes: SSIS package must be run by a user that has permissions to file.

Continue reading » · Rating: · Written on: 12-13-07 · No Comments »