• Welcome to OS2World OLD-STATIC-BACKUP Forum.
 

News:

This is an old OS2World backup forum for reference only. IT IS READ ONLY!!!

If you need help with OS/2 - eComStation visit http://www.os2world.com/forum

Main Menu

MergeMove (Exercise to improve code)

Started by jep, 2008.10.31, 09:29:13

Previous topic - Next topic

jep

Marked As: Advanced
Hello,

below you find my latest work, a script to merge folders and sub folders with a script that I hiope to be an improvement over the command "move".

Imagine the following:
You're a person that like to order things, place documents and files in categorized folders, but you have two (or more) such folder structures, of which some of the sub folders have the same name and their content should be placed together.

Example:
Sites such as http://hobbes.nmsu.edu have an ordered file structure, where they've placed files and apps in different folders. To get the structure automatically you'd e.g. use awget or plain wget and turn on the parameter to recreate the folder structure. The result is a neat and tidy directory tree with the apps you download from hobbes.

What do you do in OS/2 to merge 2 such folders? Move each files and sub folder by hand or...? That's where this code may come in handy.

The code has been tested (somewhat), but I can't promise anything, but I want you all to:

a) Look at a section at the time (which one doesn't matter) and try to understand what it does, or is supposed to do.
b) Try to see if you can find any errors or mistakes I've made
c) See if you can improve that part, e.g. remove code that we don't need, make it more robust, add another feature or support another syntax (way to call the script).
d) Post questions, suggestions etc. about your findings.
e) There's no function to show Usage! What features does this script support? Please reply with code how to write the function Usage, what one can type on the command line to use it.

//Jan-Erik

/* Script: Move all files in specified directory and subdirectories to destination (folder). */

currdir = strip( DIRECTORY(), 'T', '\' )
numwords = 0

CALL rxFuncAdd "SysLoadFuncs" , "RexxUtil" , "SysLoadFuncs"
CALL SysLoadFuncs

CALL SysCls
IF ARG() = 0 THEN
    Return Usage()
numwords = WORDS( ARG(1) )
params = ARG(1)

IF DIRECTORY( ARG(1) ) <> '' | STREAM( ARG(1), 'C', 'QUERY EXIST' ) <> ''
THEN
DO
    to = currdir
    from = ARG(1)
END
ELSE DO
/*  count = COUNTSTR( '..\', params ) is only available in Object REXX, see replacement below */
    count = 0
    strpos = POS( '..\', params )
    DO WHILE strpos > 0
        count = count + 1
        strpos = POS( '..\', params, strpos + 1 )
    END
    IF count > 0 THEN
    DO
        currpos = POS( '..\', params )
        DO i = 1 TO count
           counter = 1
           DO WHILE currpos + counter = POS( '..\', params, currpos + 3 )
               counter = counter + 1
               i = i + 1
           END
           strpos = 1
           DO j = 1 TO counter
              strpos = POS( '\', REVERSE( currdir ), strpos )
           END
           params = substr( params, 1, currpos - 1 )||substr( currdir, 1, LENGTH( currdir ) - strpos )||substr( params, currpos + counter * 3 )
        END
    END
    PARSE VALUE params WITH from '"<'to'>"' /*  */
    IF LENGTH( to ) = 0 THEN DO
        PARSE VALUE params WITH from' \'to
        IF LENGTH( to ) = 0 THEN DO
            PARSE VALUE params WITH from' \\'to
            IF LENGTH( to ) = 0 THEN DO
                PARSE VALUE params WITH from':\'to':\'rest
                IF LENGTH( rest ) > 0 THEN
                DO
                    from = from||':\'||REVERSE( SUBWORD( REVERSE( to ), 2 ) )
                    to = REVERSE( SUBWORD( REVERSE( to ), 1, 1 ) )||':\'||rest
                END
                ELSE
                DO i = 1 TO numwords
                    to = REVERSE( SUBWORD( REVERSE( params ), 1, i ) )
                    from = REVERSE( SUBWORD( REVERSE( params ), i + 1 ) )
                    IF LENGTH( from ) > 0 THEN /* DIRECTORY can't handle UNC network paths :-( */
                        IF ( DIRECTORY( from ) <> '' | STREAM( from, 'C', 'QUERY EXIST' ) <> '' ) & ( DIRECTORY( to ) <> '' | STREAM( to, 'C', 'QUERY EXIST' ) <> '' ) THEN
                            LEAVE i
                END
            END
            ELSE
                to = '\\'||to
        END
        ELSE
            to = currdir||'\'||to
    END
    ELSE to = '<'||TRANSLATE( to )'>'
END

IF LEFT( from, 1 ) = '\' & SUBSTR( from, 2, 1 ) <> '\' THEN
  from = currdir||from
IF LENGTH( from ) = 0 THEN
  from = currdir

from = STRIP( from, 'T', '\' )

CALL DIRECTORY currdir
IF LENGTH( to ) = 0 | params = to THEN
DO
    olddir = DIRECTORY( from )
    IF olddir <> '' THEN /* The directory exist, move everything from there to "here" */
    to = olddir
    ELSE IF STREAM( from, 'C', 'QUERY EXIST' ) <> '' THEN
        to = DIRECTORY() /* The file exist, move it to this folder */
END

CALL rxBusy 'Loading, one moment please...', 0

elapsed = TIME( 'R' )
CALL processPath from, strip( to,, '"' ), 0, 1
CALL rxProgress 1, 2
CALL DIRECTORY currdir
EXIT 0

processPath: PROCEDURE EXPOSE elapsed counter_
    source.path = STRIP( ARG(1),, '"' )
    target.path = STRIP( ARG(2), 'T', '\' )
    progress = ARG(3)
    chunk = ARG(4)
    SELECT
       WHEN POS( '<', source.path ) > 0 THEN /* WPS Object */
       DO
          source.type = 1
          source.path = TRANSLATE( source.path )
       END
       WHEN STREAM( source.path, 'C', 'QUERY EXIST' ) = 'READY:' THEN /* Existing file */
          source.type = 2
       WHEN DIRECTORY( source.path ) <> '' THEN /* Existing folder */
          source.type = 3
       OTHERWISE source.type = 0 /* Unknown / Doesn't exist */
    END
    IF POS( '<', ARG(2) ) > 0 THEN /* WPS Object */
        target.type = 1
    ELSE DO
        IF POS( '\', source.path ) > 0 THEN
           target.path = target.path||SUBSTR( source.path, LASTPOS( '\', source.path ) )
        IF STREAM( target.path, 'C', 'QUERY EXIST' ) = 'READY:' THEN /* Already existing file */
            target.type = 2 /* You're not allowed to specify the target file name! */
        ELSE IF DIRECTORY( target.path ) <> '' THEN /* Already existing folder */
            target.type = 3
        ELSE target.type = 0 /* Unknown / Doesn't exist */
    END
    CALL DIRECTORY STRIP( FILESPEC( 'D', source.path ), 'T', '\' )||'\'

    IF source.type = 2 & target.type > 0 THEN /* Can't move the file */
        SAY ARG(2)||" already exist"
    ELSE IF source.type = 3 & target.type = 2 THEN  /* Can't move the folder */
        SAY "Can't move the folder "||source.path||" to "||ARG(2)||" as it is a file."
    ELSE IF source.type = 1 & target.type = 2 THEN  /* Can't move the object */
        SAY "Can't move the object "||source.path||" to "||ARG(2)||" as it is a file."
    ELSE IF target.type = 2 THEN  /* Can't move to a folder */
        SAY "Can't move "||source.path||" to "||ARG(2)||" as it is a file."
    ELSE IF SysMoveObject( source.path, STRIP( ARG(2), 'T', '\' ) ) = 0 THEN
    DO
        IF source.type = 1 | target.type = 1 THEN
           SAY "Couldn't move "||FILESPEC( 'N', source.path )||" to "||ARG(2) /* Couldn't move the object, Improvement possible (query object path and continue) */
        ELSE DO
           CALL SysFileTree source.path||'\*', 'folder', 'DO'
           IF target.type = 1 | target.type = 3 THEN
               CALL SysFileTree source.path||'\*', 'file', 'FO'
           ELSE file.0 = 0
           DO j = 1 to folder.0
               IF TIME( 'E' ) > 1 + elapsed THEN DO
                   CALL rxBusy 'Processing '||folder.j, 0
                   CALL rxProgress progress + ( j - 1 ) / ( folder.0 + file.0 ) * chunk, 2
                   elapsed = TIME( 'E' )
               END
               CALL processPath folder.j, target.path, progress + ( j - 1 ) / ( folder.0 + file.0 ) * chunk, chunk / ( folder.0 + file.0 )
           END
           DROP folder.

           IF target.type = 1 | target.type = 3 THEN
           DO
               DO i = 1 to file.0
                   IF TIME( 'E' ) > 1 + elapsed THEN DO
                       CALL rxBusy 'Processing '||source.path, 0
                       CALL rxProgress progress + ( j + i - 1 ) / ( j + file.0 ) * chunk, 2
                       elapsed = TIME( 'E' )
                   END
                   rc = SysMoveObject( file.i, target.path )
               END
               DROP file.
           END
        END
    END
    ELSE CALL rxBusy FILESPEC( 'N', source.path )||' has been moved to '||ARG(2), 0
RETURN

/* part, of_total, display_on_row */
rxProgress: PROCEDURE
    IF DATATYPE( ARG(1), 'N' ) THEN
    DO
        progress = MIN( TRUNC( 76 * ARG(1) ), 76 )
        CALL rxOut LEFT( COPIES( '█', progress )||COPIES( '░', 76 - progress ), 76 )||RIGHT( TRUNC( 100 * ARG(1) )||'%', 4 ), ARG(2)
    END
Return 0

rxBusy: PROCEDURE EXPOSE counter_
    SELECT
        WHEN counter_ = 1 THEN
            CALL rxOut '/ '||ARG(1), ARG(2)
        WHEN counter_ = 2 THEN
            CALL rxOut '- '||ARG(1), ARG(2)
        WHEN counter_ = 3  THEN
            CALL rxOut '\ '||ARG(1), ARG(2)
        OTHERWISE
        counter_ = 0
        CALL rxOut '| '||ARG(1), ARG(2)
    END
    counter_ = counter_ + 1
Return 0

/* text, display_on_row */
rxOut: PROCEDURE
    IF DATATYPE( ARG(2), 'W' ) THEN
        PARSE VALUE SysCurPos( ARG(2), 0 ) with prev_row prev_col
    SAY LEFT( ARG(1), 80 )
    IF DATATYPE( ARG(2), 'W' ) THEN
        CALL SysCurPos prev_row, prev_col
Return 0

jep

#1
Hello,

The code has been modified to work in Classic REXX as well.
I used a function (COUNTSTR) to count occurrences of a string within another string, it's very convenient to use, but possible to replace with a few rows of code. See below.

//Jan-Erik

/*  count = COUNTSTR( '..\', params ) is only available in Object REXX, see replacement below */
    count = 0
    strpos = POS( '..\', params )
    DO WHILE strpos > 0
        count = count + 1
        strpos = POS( '..\', params, strpos + 1 )
    END

jep

Hello,

You may have noticed that the script above doesn't allow you to specify what to do with existing files (nor folders). The script just continues as it can't move things, if it's a folder it will list things within and try to move those instead, while it leave files as they are.

You can therefore create one or more functions that take care of that for you, depending on how much control you want to have and how many questions you want to answer when it can't copy files/folders.

The code below just contain the more basic questions how to handle files, but I would appreciate if you rewrite the code and present it here so that it also can handle more situations, such as when the user want to "Answer yes to all questions" and "Answer no to all questions" once and for all.

You should probably place that code (answer once and for all) together with the IF-statement below, right after:
rc = SysMoveObject(...
IF \rc THEN
                   CALL ReplaceRenameObject


Then you create the functions futher down that you code similar to this:

ReplaceRenamObject: PROCEDURE file.
                       source.size = STREAM( file.i, 'C', 'QUERY SIZE' )
                       source.datetime = STREAM( file.i, 'C', 'QUERY DATETIME' )

                       target.size = STREAM( target.path||FILESPEC( 'N', file.i ), 'C', 'QUERY SIZE' )
                       target.datetime = STREAM( target.path||FILESPEC( 'N', file.i ), 'C', 'QUERY DATETIME' )
                       IF target.size > 0 THEN
                       DO
                           IF source.size > target.size THEN
                               CALL rxBusy 'The file '||FILESPEC( 'N', file.i )||' is larger ('||source.size||' b) than the existing version ('||target.size||' b) with the same name. '
                           IF source.size < target.size THEN
                               CALL rxBusy 'The file '||FILESPEC( 'N', file.i )||' is smaller ('||source.size||' b) than the existing version ('||target.size||' b) with the same name. '
                           IF source.datetime > target.datetime THEN
                               CALL rxBusy 'The new file '||FILESPEC( 'N', file.i )||' is newer ('||source.datetime||' than the existing version ('||target.datetime||').'
                           ELSE IF source.datetime < target.datetime THEN
                               CALL rxBusy 'The file '||FILESPEC( 'N', file.i )||' is older ('||source.datetime||' than the existing version ('||target.datetime||').'
                               CALL rxBusy 'Do you want to:'
                               CALL rxBusy '<R>eplace the old file with the new, or'
                               CALL rxBusy 'rename the <N>ew file, or'
                               CALL rxBusy 'rename the <O>ld file, or'
                               CALL rxBusy '<S>kip the file? ( R, N, O, S )'
                               Key = TRANSLATE( SysGetKey() )
                               IF Key = 'R' THEN
                               DO
                                  rc = SysFileDelete( target.path||FILESPEC( 'N', file.i ) )
                                  rc = SysMoveObject( file.i, target.path )
                               END
                               ELSE IF Key = 'N' THEN
                               DO
                                  SAY 'Enter new file name:'
                                  PARSE PULL filename
                                  IF rxReName( file.i, filename ) = 0 THEN
                                     IF SysMoveObject( FILESPEC( 'D', file.i )||FILESPEC( 'P', file.i )||filename, target.path ) = 0 THEN
                                        SAY 'The new file '||FILESPEC( 'N', file.i )||' has been renamed to '||filename
                               END
                               ELSE IF Key = 'O' THEN
                               DO
                                  SAY 'Enter new file name:'
                                  PARSE PULL filename
                                  IF rxReName( target.path||FILESPEC( 'N', file.i ), filename ) = 0 THEN
                                     IF SysMoveObject( file.i, target.path ) = 0 THEN
                                        SAY 'The existing file '||FILESPEC( 'N', file.i )||' has been renamed to '||filename
                               END
                       END
RETURN 0


I couldn't find a built in or RexxUtil function to rename files, so you may try the code below until you or someone else can suggest something better.

rxReName: PROCEDURE
SIGNAL OFF FAILURE
SIGNAL OFF ERROR
SIGNAL OFF NOTREADY
'@RENAME "'||ARG(1)||'" "'||ARG(2)||'"'
Return rc


//Jan-Erik