• RE: Result Limit

    Phil,

    Just off the top of my head, the use case could simply be that a certain business has natural chunks of N number of documents that is greater than 100 but less than, say, 2000. I can't think of why that would be, but can understand that certain queries (especially if Workflow or Task related) might always end up returning a number of records greater than 100 but less than 10,000. Having a pager limit of something higher than 100 would make sense, then.

    But by and large, web-based clients tend to offer pager limits that are lower than higher, like 50, 75, and 100. That being said, I have loaded Folders with 2000-3000 documents and they load acceptably. Options of 500 and 1000 per page could be offered, at least in on-premise installations where the additional resources to present that data are all local.

    I can see why a lower pager limit is desirable when it comes to a multi-tenant cloud situation -- you want the server to be as piecemeal as possible so larger chunks of data don;t start to bog things down.

    Thanks,
    Joe Kaufman
  • RE: Using MS SQL Statement for File Cabinet Profile

    Daryl,

    You are still including the " <> 'Public' in the first statement, which you don't need to. Your statement should instead look like:
    DEPARTMENT IN (SELECT SUBSTRING(name, 1, CHARINDEX('-', name, 1) - 1) AS Department FROM dwsystem.dbo.dwgroup
        WHERE gid IN (SELECT gid FROM dwsystem.dbo.DWUserToGroup WHERE uid IN (SELECT uid FROM dwsystem.dbo.dwuser WHERE name = 'bsikora'))
        AND CHARINDEX('-', name, 1) > 0)

    If that works, change <'bsikora"> back to <CURRENTUSERLONGNAME()> (without the brackets) and you are all set. You should still always leave the check in there to make sure the group name has a dash, and you should remove the check for group name not being "Public".

    If you still get back no data, then your statement is correct, it just either isn't finding that username, or your group names do not actually have dashes in them. Did you get a list of group names with query:
     
    SELECT name FROM dwsystem.dbo.dwgroup

    and did you verify the group names are what you anticipate? And you are sure that user is in the group you state? I am not sure what else to look for if the SQL query is syntactically correct. I am able to run queries for users in our system and get a list of group names back properly, using a space as my delimiter.  What if you just run the query:
     
    SELECT Name AS GroupName FROM dwsystem.dbo.dwgroup
        WHERE gid IN (SELECT gid FROM dwsystem.dbo.DWUserToGroup WHERE uid IN (SELECT uid FROM dwsystem.dbo.dwuser WHERE name = 'bsikora'))

    ? Do you get back a list of all groups bsikora resides in? Do those group names have the expected dashes in them?

    Thanks,
    Joe Kaufman





     
  • RE: Result Limit

    David,

    Do note that if you define a Folder, it will list everything that matches the Folder criteria in one screen. In this fashion you can get over the 100-record limit, but it also means pages can tend to load rather slowly if there are thousands of records...

    Thanks,
    Joe Kaufman
  • RE: Using MS SQL Statement for File Cabinet Profile

    Daryl,

    The problem here is that if any group name doesn't have a colon in it, the SUBSTRING() command is trying to use a negative number when CHARINDEX() comes up with zero. CHARINDEX() will return zero if it doesn't find the character you are looking for.

    So, what are the names of your groups? Find this out by performing the query:
     

    SELECT name FROM dwsystem.dbo.dwgroup


    I don't have any group names with colons, but all my group names have spaces in them. If I change the colon in your query to a space, the query succeeds.

    In the same way you keep "Public" out (ostensibly because it doesn't have a colon), you need to keep out any group name lacking a colon via another condition in the WHERE clause:
     

    AND CHARINDEX(':', name, 1) > 0


    In fact, since that will inherently keep out "Public" as well, you can just use that for a final query of:
     

    DEPARTMENT IN (SELECT SUBSTRING(name, 1, CHARINDEX(' ', name, 1) - 1) AS Department FROM dwsystem.dbo.dwgroup
        WHERE gid IN (SELECT gid FROM dwsystem.dbo.DWUserToGroup WHERE uid IN (SELECT uid FROM dwsystem.dbo.dwuser WHERE name = CURRENTUSERLONGNAME()))
        AND CHARINDEX(':', name, 1) > 0)


    As I said, I get no records back (because I have no groups with colons in the name), but at least the query does not error out on SQL Server.

    Good luck!
    Joe Kaufman

  • RE: DocuWare Platform .NET API - Checking Documents In/Out

    Edward,

    Some other async examples make the operation become synchronous by asking for the Result of the operation. Consider the example of downloading a document, found here:

    https://developer.docuware.com/dotNet_CodeExamples/4354370d-a0ce-42d4-ba94-6dc848ed62c0.html

    The main call is an async method, but the Result is summoned, making the program wait:

                var downloadResponse = document.PostToFileDownloadRelationForStreamAsync(
                    new FileDownload()
                    {
                        TargetFileType = FileDownloadType.Auto
                    }).Result;


    This gives you back the stream immediately for download.

    I am not sure other async routines are written in such a way that this will always work, and async stuff makes my head hurt.

    A really quick and dirty way to know if the file is done downloading would be to wait for the file to exist, and then try to lock it for exclusive write. That shouldn't be allowed while the file is still downloading. I have a WaitForFile routine I use for that:
     

            /// <summary>
            /// Like the global VFP function of the same name, this routine waits for a file to appear and not be in use elsewhere (via exclusive write check)
            /// so that the file is known to be available for use and no longer being filled or operated on by any other process.
            /// </summary>
            /// <param name="fileName">The file name to wait for.</param>
            /// <param name="timeout">Milliseconds to wait for the file (forever, by default).</param>
            /// <param name="silent">If true, do not display any information to use, not even if timeout is hit.</param>
            /// <param name="prePauseSeconds">A time to pause (in seconds) before checking for the file, to give the file time to appear regardless of timeout.</param>
            /// <returns>Returns TRUE if file is found and free of other use within timeout period, otherwise FALSE.</returns>
            public static bool WaitForFile(string fileName, int timeout = Int32.MaxValue, bool silent = false, int prePauseSeconds = 0)
            {
                bool fileFound = false;
                int numLoops = 0;
                if (fileName.Length == 0) { return false; }
                if (timeout <= 0)
                {
                    // A timeout has been passed in but is zero or less. Assume this is meant to be an infinite wait.
                    timeout = Int32.MaxValue;
                }
                if (prePauseSeconds > 0)
                {
                    System.Threading.Thread.Sleep(prePauseSeconds * 1000);
                }
                // We can now wait for the file to appear.
                while ((!fileFound) && (numLoops <= timeout))
                {
                    if (File.Exists(fileName))
                    {
                        fileFound = true;
                    }
                    else
                    {
                        // Wait one second before trying again...
                        System.Threading.Thread.Sleep(1000);
                        Application.DoEvents();
                        if ((numLoops >= 5) && (!silent))
                        {
                            UIFuncs.StartWait("Waiting for file " + fileName + " " + numLoops.ToString() + " times...");
                        }
                        numLoops++;
                    }
                }
                // If file not found, then we have timed out.
                if (!fileFound)
                {
                    if (!silent) { UIFuncs.StopWait(); }
                    return false;
                }
                // If we make it here, we have file, but it might still be growing or in use.
                // Wait until nothing else has it in use.
                numLoops = 0;
                while (FileInUse(fileName) && (numLoops <= timeout))
                {
                    // Wait one second before trying again...
                    System.Threading.Thread.Sleep(1000);
                    Application.DoEvents();
                    if ((numLoops >= 5) && (!silent))
                    {
                        UIFuncs.StartWait("Waiting for file " + fileName + " " + numLoops.ToString() + " times...");
                    }
                    numLoops++;
                }
                if (!silent) { UIFuncs.StopWait(); }
                return (numLoops <= timeout);
            }
    


    Never mind the "StartWait" stuff, that is just a popup display I developed to mimic FoxPro's WAIT WINDOW functionality. The above routine calls the method FileInUse, which looks like this:

            /// <summary>
            /// Check to se if a file is in use by trying to open it with exlusive write access (that lock is removed if access is granted).
            /// </summary>
            /// <param name="fileName">The file name to check for being in use.</param>
            /// <returns>Returns TRUE if the file is in use elsewhere, FALSE otherwise.</returns>
            public static bool FileInUse(string fileName)
            {
                bool inUse = true;
                FileStream stream = null;
                try
                {
                    FileInfo fi = new FileInfo(fileName);
                    stream = fi.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
                    inUse = false;
                }
                catch (IOException)
                {
                    // File is in use by another process.
                    inUse = true;
                }
                finally
                {
                    if (stream != null) { stream.Close(); }
                }
                return inUse;
            }
    


    Maybe that would work for you, assuming you are doing this in C#/.NET, of course.

    Thanks,
    Joe Kaufman




     

  • RE: webservices uploading a 50mb file

    Raymond,

    I don't know about overhead, all I ever did was time the uploads. The "Easy" method is a bit slower (but not much, as I recall), which is why I made a threshold to not use it unless a document was over a certain size.

    If there is overhead, I imagine it would only be on the client, since that is where the file is being split out and sent in smaller chunks. Though, I suppose the server has to use a few more resources to wait for the chunks and put the file together again before saving. My knowledge of HTTP and IIS is woefully in adequate to determine whether or not there is a perceptible difference.

    Glad it worked for you, in any case, since even if it does take more resources it has to be done to get these large files uploaded!

    Thanks,
    Joe Kaufman
  • RE: webservices uploading a 50mb file

    Raymond,

    According to the documentation found here:

    https://developer.docuware.com/sdk/platform-eagle/html/57da87ed-111c-4f78-96e9-75d3b9462ce9.htm

    and

    https://developer.docuware.com/sdk/platform-eagle/html/c3431d10-b73e-4b98-814a-a0d235d21f80.htm

    there are two different upload methods you can use, and one is meant for larger files.

    Here is C# code I use to upload files, and I set the threshold at 10 MB:

     
            public static Document UploadFileToFileCabinet(FileCabinet fileCabinet, string uploadFile, Document indexInfo = null)
            {
                LastError = "";
                try
                {
                    // We will use a standard upload method for smaller files, but the "Easy" version for larger files, otherwise
                    // large file uploads might fail (the "Easy" version is meant for huge file uploads, according to the documentation).
                    int largeFileThresholdInBytes = 10 * 1024 * 1024;    // 10 MB
                    Document uploadedDoc = null;
                    FileInfo fileInfo = new FileInfo(uploadFile);
                    if (fileInfo.Length < largeFileThresholdInBytes)
                    {
                        // Smaller file.
                        uploadedDoc = fileCabinet.UploadDocument(indexInfo, fileInfo);
                    }
                    else
                    {
                        // Larger file.
                        uploadedDoc = fileCabinet.EasyUploadSingleDocument(fileInfo, indexInfo);
                    }
                    return uploadedDoc;
                }
                catch (Exception ex)
                {
                    LastError = ex.Message;
                    return null;
                }
            }

    The "Easy" method is part of some extensions, though, so I am not sure it has a direct resource end-point when doing API calls via non-NET environments.

    Are you using .NET to access the API? If not, what are you using? I see in my non-NET code (Visual FoxPro) that if I need to upload a large file I shell out to the .NET program I wrote to upload files instead of using the straight HTTP endpoint to upload. So, I must have run into the same issue you are facing. The only truly reliable way to upload huge files is with the .NET code. With that code I have uploaded files greater than 1 GB in size.

    Thanks,
    Joe Kaufman


     
  • RE: REST API - Search on Dates and Empty date field

    Frederic,

    Good info! I did not know the EMPTY() keyword could be used (or else I did know a long time ago and forgot *smile*).

    Just FYI for anyone else reading the thread, you can make the search be for a range of dates by simple adding a second "Value", like this:
     
    <?xml version="1.0" encoding="utf-8"?>
    <DialogExpression xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Operation="And" xmlns="http://dev.docuware.com/schema/public/services/platform">
        <Condition DBName="SHIP_DATE">
            <Value>2019-01-01</Value>
            <Value>2019-12-31</Value>
        </Condition>
    </DialogExpression>
    This is a query on a field called "SHIP_DATE", and would get everything where ship date was in 2019.

    For a full date/time field, such as DWSTOREDATETIME, the XML looks like:
     
    <?xml version="1.0" encoding="utf-8"?>
    <DialogExpression xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Operation="And" xmlns="http://dev.docuware.com/schema/public/services/platform">
        <Condition DBName="DWSTOREDATETIME">
            <Value>2020-01-03T00:00:00</Value>
            <Value>2020-01-03T23:59:59</Value>
        </Condition>
    </DialogExpression>

    This would get anything stored on January 3, 2020.

    Incidentally, you can use the full date/time syntax on a field that is just a "date" type and it works fine. DocuWare stores Date and DateTIme fields the same way in the database (at least in SQL Server).

    Thanks,
    Joe Kaufman



     
  • RE: Search documents based only on Docuware's document ID

    Phil,

    True enough, but not everyone can just buy a new module any time a new business need arises. I could write a console app that does this in probably a couple of hours and then set it to run every five minutes on whatever infrastructure we have developed to run automated tasks. If done right, one would use a straight SQL Server query to see where the new document ID field was zero, and then just populate that field for those documents from DWDOCID using the Platform SDK. Most IT shops have a machine (or some process) set up to run automated tasks.

    Of course, if this is a high-volume situation or a scenario where "the sooner the better" when it comes to that field being populated, there is little doubt AIX is the way to go, and probably the most cost effective in the long term. I don't know that Patricia has those needs, though.

    Now, if DocuWare would simply have AutoIndex built in, that would settle the whole issue!  *smile*

    Thanks,
    Joe Kaufman
  • RE: Search documents based only on Docuware's document ID

    Hey all,

    What about adding a DocID field to the cabinet, and then using the Platform SDK to populate it from the DWDOCID system field? This results in no custom access of the database and keeps everything in sync since the platform uses the same business logic as changing an index field from the web client.

    Patricia, is use of the Platform SDK an option? Setting this up as a console .NET application that gets set to run at regular intervals would be fairly straightforward.

    Thanks,
    Joe Kaufman