Tuesday, October 24, 2017

Why can't Amazon property Audible manage basic search for user libraries?

RANT!
tldr; Amazon's property Audible can't do the basic keyword search for user's libraries.
More:
So More of a pet peeve of mine - how can Audible, an AMAZON property, fail to provide even the most basic keyword search capability for user libraries? Their answer is you MUST know the exact title of EVERY book you have ever purchased to find it in your library. My latest correspondence trying to follow up on open ticket:

===================

Jash:You need to search for the exact title.
Me:WRONG
That is a broken BAD search.
The site search doesn't work that way
Amazon search for Kindle doesn't work that way
No OTHER search works that way
WHY does Audible work that way? And note that I am a search engine professional and WILL use your answer in a paper on how AMAZON affiliates intentionally fail to provide ability to manage content (via search).
So, care to punt me to your boss "Jash" or is this Audible's response for why search for user libraries doesn't work?
Jash:Let me check this one moment.
Me:And Yes, other reps have said they would escalate and get back to me x3 times and have NOT. so waiting.
Jash:One moment please.
Upon checking here that the last representative escalates this and created a ticket, what can I do in this is to update the escalation and you don't have to worry the technical specialist team is now handling this concern.
Me:BUT no one has ever gotten back to me on fixing this; it's been many months. I and many other search professionals could fix this problem with open source Solr within a week or so. SO WHAT is the dealy?
(The answer seems to be that Audible DOES NOT want users to find their content so that they will needlessly purchase different variations of books).
As reference, I have nearly 630 books in Audible - expecting me to KNOW the EXACT title of every book is crazy.
Jash:Because the technical specialist team is still working on it and you don' have to worry because this concern was already escalated.
Me:What is the technical ticket number for reference - my blog audience will want to know?
AND also, why has no one ever followed up via email as promised?
(and if it's not clear, I don't believe you - this sounds like a pat answer that you give anyone who complains to end the chat session.)
Jash:This is the ticket number "0125012015" and you need to wait patiently for the specialist team to resolved this issue.
Me:Okay - how long should I wait? It's been more than 30 days since last raised the question. WHEN will it be delivered?
Jash:I'm sorry but in this situation the account specialist is the one who is handling this issue and we don't have time frame for this.
Me:Okay, so not fixing anytime soon (or not that you will commit to). I'll post our correspondence in facebook and blog to help get more users involved in fixing this issue. Sorry, you wouldn't escalate to your boss, hope they have a nice day. (NO ONE will believe for a moment that an AMAZON property can't manage basic library search.)
Jash:Thank you for understanding.
Is there anything else I can help you with today?


=====================
And later, following up on the ticeket

=====================

Karina:thank you
let me check on that ticket case
ok rick so far the department is still working on fixing this at the moment
the last update was that they are working on getting the case sensitive fix with our website coding deparment
just like the main search library
Me:Can you clarify, for my blog audience, (a) when the ticket was open and (b) when/what release it is expected to be addressed?
I ask since it has been more than 6 months that I've opened my first ticket and three times since and no progress.
and no follow up
Note that the issue is not just case sensitive; it's searching on series names, author and narrator names, and basically anything else that works on the site search that doesn' work on the user library search
or any key word
Karina:yes Rick actually your ticket was added to master ticket that include other customers account and so far they are working on fixing this issue for now they are making sure the work each coding at the time. but as soon as they get that fix trust us we will contact you and let you know
the ticket was reported on September 19
Me:Example, search on 'ring' or 'rings' does NOT find any of my lord of the rings books - even though that is clearly in the description /series.
Hmmm pretty sure I reported this at the beginning of the year; September was just the latest (and the rep did not follow up with me either). Is a new ticket opened for each complaint? How many people have complained about this?
Karina:I actually don't have access to that information but they do say here on your ticket that they added you to the master ticket. and checking on your account and the report was on that date only
Me:So - when do I expect a fix? Or should I keep posting requests every week?
Karina:I'm sorry rick for this inconvenience but we don't have a time that it will be fix. but we are making sure it does and we are working on it. as soon as it's fix we will let you know
Me:sigh. I can only think that this problem is not being fixed because Audible doesn't want it to be fixed. There are a lot of very good search engine specialists (check LinkedIn) who could fix this problem in a week (see https://www.searchtechnologies.com for one of my recommended vendors).
Anyway, you can escalate my request to management?
As a reference, with around 650 audible books, I've spent around $7,000 USD and kind of expect some level of support.
Other than "we are working on it"
Karina:I understand Rick and I'm sorry to hear the your unhappy with this, but i can reassure you that the issue is been look into.
Me:Okay - when should I follow up when it is not fixed (again)
Karina:but I will make sure the your concern is report it
i don't have a estimate time I'm sorry
best recommendation will be to wait for our contact with any update
Me: Seriously? Don't call us, we'll call you?
Karina:I understand your frustration and again i apologies for this inconvenience but we don't have control on how they can fix this. now we do know that the account specialist as working on it and making sure every code and system get fix for the website
Me:Sigh. I bet you a dollar it's won't go anywhere in the next 30 days. Care to wager?
Karina:Sorry Rick but like I said i will report you contact today and let them know that you reported the issue again to please give you a update as soon as they can
Rick anything else i can help you with?
Me:No. Your management is setting you up to look foolish saying it's taking months to fix a problem that has been fixed for more than 25 years everywhere else.
Time to fresh up the resume. Better jobs out there. Good luck
Karina:I understand, Sorry Rick thank you for your feedback
Thank you for contacting Audible Chat Support and have a great day! If you would like additional assistance, please do not hesitate to contact us. We are here to help 24 hours a day, 7 days a week.

===========================

Dang! Now I feel bad for the chat (bot?) who has to provide the response to user requests. 

I ask you - Should we expect an industry leading consumer site to provide basic service more than 25 YEARS after AltaVista proved they could search for keywords rather than dumb database exact text matches?





Monday, July 29, 2013

Welcome Nimitz



My  Rat Terrier puppy named Nimitz.





Wednesday, July 17, 2013

Using Microsoft.Office.Interop.PowerPoint to convert PPT to PDF

Microsoft PowerPoint can create some pretty good PDF using it's built in Save As functionality. The Interop library allows to code this functionality for large batch jobs.

One notable issues is that trying to open a password protected file, where the password wasn't known and would have preferred a graceful fail as with the Interop.Word library, would pop open a password dialog box and block the batch processing until a user could manually click on cancel.

After a few days of trial and error, I got the following advice from friends at StackOverflow.com - just append a bogus password to the file name when opening the PPT in the form of "c:\temp\filename.ppt::BOGUS_PASSWORD::" - if the PPT file required a password it is ignored, if it's the wrong password it throws an exception, but no password prompt is open on the screen.

Very nice


using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using Microsoft.Office;
using Microsoft.Office.Interop.PowerPoint;

using MT = Microsoft.Office.Core.MsoTriState;

namespace PowerPointConverter
{
 public class PowerPointConverter : IDisposable
 {
  Application app;
  public PowerPointConverter()
  {
   app = new Microsoft.Office.Interop.PowerPoint.Application();
   app.DisplayAlerts = PpAlertLevel.ppAlertsNone;
   app.ShowWindowsInTaskbar = MT.msoFalse;
   app.WindowState = PpWindowState.ppWindowMinimized;
  }

  public bool ConvertToPDF(FileInfo sourceFile, DirectoryInfo destDir)
  {
   bool success = true;
   

   FileInfo destFile = new FileInfo(destDir.Name + "\\" +
    Path.GetFileNameWithoutExtension(sourceFile.Name) + ".pdf");

   Thread pptThread = new Thread(delegate()
   {
    try
    {
     string openString = sourceFile.FullName + "::BOGUS_PASSWORD::";
     
     Presentation ppt = null;
     ppt = app.Presentations.Open(openString, MT.msoTrue, MT.msoTrue, MT.msoFalse);
     ppt.SaveAs(destFile.FullName, PpSaveAsFileType.ppSaveAsPDF, MT.msoFalse);
     ppt.Close();
     System.Runtime.InteropServices.Marshal.FinalReleaseComObject(ppt);
    }
    catch (System.Runtime.InteropServices.COMException comEx)
    {
     success = false;
    }
   });

   pptThread.Start();
   if (!pptThread.Join(20000))
   {
    pptThread.Abort();
    success = false;
   }

   return success;
  }

    
  public void Dispose()
  {
   Thread appThread = new Thread(delegate()
   {
    try
    {
     if (null != app)
     {
      app.Quit();
      System.Runtime.InteropServices.Marshal.FinalReleaseComObject(app);
     }
    }
    catch (System.Runtime.InteropServices.COMException) { }
   });

   appThread.Start();
   if (!appThread.Join(10000))
   {
    appThread.Abort();
   }
  }
 }
}

Thursday, June 13, 2013

Convert Word documents using Interop API

Have a requirement to convert millions of documents to html, preserving the formatting and style, so trying out Microsoft.Office.Interop.Word's SaveAs API.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Word = Microsoft.Office.Interop.Word;
using Microsoft.Office.Interop.Word;

public class WordTool: IDisposable
  {
    Word._Application oWord;
    object oMissing = System.Reflection.Missing.Value;
    object isVisible = true;
    object readOnly = true;
    object oSaveChanges = false;

    public WordTool()
    {
      // Create an instance of Word.exe
      oWord = new Word.Application();
      oWord.Visible = false;
      oWord.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
    }

    public void Convert(string input, string output)
    {
      WdSaveFormat format;
      switch (Path.GetExtension(output.ToLower()))
      {
        case ".doc":
          format = WdSaveFormat.wdFormatDocument;
          break;
        case ".docx":
          format = WdSaveFormat.wdFormatDocumentDefault;
          break;
        case ".htm":
          format = WdSaveFormat.wdFormatHTML;
          break;
        case ".html":
          format = WdSaveFormat.wdFormatFilteredHTML;
          break;
        case ".pdf":
          format = WdSaveFormat.wdFormatPDF;
          break;
        case ".rtf":
          format = WdSaveFormat.wdFormatRTF;
          break;
        case ".mht":
          format = WdSaveFormat.wdFormatWebArchive;
          break;
        case ".xps":
          format = WdSaveFormat.wdFormatXPS;
          break;
        case ".txt":
          format = WdSaveFormat.wdFormatTextLineBreaks;
          break;
        case ".xml":
          format = WdSaveFormat.wdFormatFlatXML;
          break;
        default:
          format = WdSaveFormat.wdFormatText;
          break;
      }

      object oFormat = format;
      object oInput = input;
      object oOutput = output;

      // Load a document into our instance of word.exe
      Word._Document oDoc = oWord.Documents.Open(ref oInput,
        ref oMissing, ref readOnly, ref oMissing,
        ref oMissing, ref oMissing, ref oMissing,
        ref oMissing, ref oMissing, ref oMissing,
        ref oMissing, ref isVisible, ref oMissing,
        ref oMissing, ref oMissing, ref oMissing);

      // Make this document the active document.
      oDoc.Activate();

      // Save this document in Word 2003 format.
      oDoc.SaveAs(ref oOutput, ref oFormat,
        ref oMissing, ref oMissing, ref oMissing,
        ref oMissing, ref oMissing, ref oMissing,
        ref oMissing, ref oMissing, ref oMissing,
        ref oMissing, ref oMissing, ref oMissing,
        ref oMissing, ref oMissing);

      // found temp instance of doc if not closed
      oDoc.Close(ref oSaveChanges, ref oMissing, ref oMissing);
    }

    public void Dispose()
    {
      if (null != oWord)
        oWord.Quit(ref oSaveChanges, ref oMissing, ref oMissing);
    }
  }

This solution based on code originally found on Stack Overflow

Thursday, April 18, 2013

C# in memory XSLT processing

Needed to create an in memory (stream) based XSLT, was frustrated all the ready examples were file based, so documenting my solution for future reference and to share the love.

The convenience wrapper that references string names:


public static string DoXslTransform(string xslPath, string xmlBase, string relativeUri)
{

    XslCompiledTransform transform = 
      GetXslCompiledTransform(xslPath);

    Uri baseUri = new Uri(xmlBase);

    XmlReader xmlReader = GetXmlReader(baseUri, relativeUri);

    string data = GetXslToString(transform, xmlReader);

    xmlReader.Close();

    return data;
}

Method to create a stream based XmlReader:


public static XmlReader GetXmlReader(Uri baseUri, string relativeUri)
{
    XmlUrlResolver xmlUrlResolver = new XmlUrlResolver();

    xmlUrlResolver.Credentials = 
      System.Net.CredentialCache.DefaultCredentials;

    Uri fulluri = 
      xmlUrlResolver.ResolveUri(baseUri, relativeUri);

    Stream stream = (Stream)
      xmlUrlResolver.GetEntity(fulluri, null, typeof(Stream));
    return XmlReader.Create(stream);
}

Method to create a compiled transformer:


public static XslCompiledTransform GetXslCompiledTransform(string xslPath)
{
    XslCompiledTransform transform = new XslCompiledTransform();

    XmlReaderSettings xmlReaderSettings = 
      new XmlReaderSettings();
    xmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit;
    xmlReaderSettings.CloseInput = true;

    XsltSettings xsltSettings = new XsltSettings(true, true);

    XmlResolver secureResolver = 
      new XmlSecureResolver(new XmlUrlResolver(), xslPath);

    transform.Load(XmlReader.Create(xslPath, xmlReaderSettings), 
      xsltSettings, secureResolver);

    return transform;
}

Method to do the transformation to string:


public static string GetXslToString(XslCompiledTransform transform, XmlReader xmlReader)
{
    MemoryStream memoryStream = 
      new MemoryStream();
    StreamWriter outStreamWriter = 
      new StreamWriter(memoryStream);

    transform.Transform(xmlReader, null, outStreamWriter);

    outStreamWriter.Flush();
    memoryStream.Position = 0;

    StreamReader reader = new StreamReader(memoryStream);
    
    return reader.ReadToEnd();
}